京东科技面经分享:浏览不吃亏,收藏拿offer

195 阅读7分钟

今天参加了京东的一面,面试我的是一个特别儒雅的大哥,并没有问很多的八股文,而是问了很多实际开发中会遇到的问题,面试水平很高!!话不多说,今天想分享几个被问到的问题,我觉得我当时回答的不是很好

1. js 如何实现深拷贝,如果深拷贝过程中遇到循环引用的问题,如何处理

这个问题其实重点在于后面,我当时实在没有想起来,开车回来的路上就一直在想这个问题,我把我的思路写下来,仅供参考,欢迎讨论

首先,实现一个简单的深拷贝就不说了,网上大片都是,我们以此为起点,开始思考

const deepCopy = source => {
  // 模块一:判断是否为数组
  const isArray = arr => Object.prototype.toString.call(arr) === '[object Array]'
​
  // 模块二:判断是否为引用类型
  const isObject = obj => obj !== null && (typeof obj === 'object' || typeof obj === 'function')
​
  // 模块三拷贝(递归思路)
  const copy = input => {
    if (typeof input === 'function' || !isObject(input)) return input
​
    const output = isArray(input) ? [] : {}
    for (let key in input) {
      if (input.hasOwnProperty(key)) {
        const value = input[key]
        output[key] = copy(value)
      }
    }
​
    return output
  }
​
  return copy(source)
}
​

在拷贝过程中,其实核心就是模块三,递归遍历,但是这个代码存在一些问题,比如面试官问我的循环引用的问题,我当时的思路是首先判断循环应用有没有出现,然后终止这种行为,不要让循环引用导致内存泄露,进而崩溃,那么顺着往下想

循环应用为什么会导致崩溃?

是因为陷入了死循环。

为什么会陷入死循环?

如果是人的话,在两个屋子来回走,人会陷入死循环吗,不会!因为我知道这个房间我已经来过了,那么换成代码,如何避免死循环,就是让代码知道,这个元素已经遍历过了就可以了

到这里其实思路已经清晰了,我们需要一个哈希表来记录这个元素已经遍历过了

那么用什么结构来做这个哈希表呢

方案一:用普通的object作为哈希表

const cache = {}; // 用对象存储
function clone(obj) {
   if (cache[obj]) return cache[obj];
   // ...
}

这里第一个问题就是对象的键值是强制转为string的

const objA = { name: 'A' };
const objB = { name: 'B' };
objA.link = objB;
objB.link = objA;
​
const cache = {}; // 用普通对象做缓存
​
cache[objA] = '克隆A';
cache[objB] = '克隆B';
​
// 键值自动转换
console.log(objA.toString()); // "[object Object]"
console.log(objB.toString()); // "[object Object]"
​
// 实际存储的键是相同的!
console.log(cache); // { "[object Object]": "克隆B" }

第二个问题,用对象作缓存,缓存会长期持有对象引用,阻止浏览器GC回收

方案二:Array存储对象引用

const arr = [];
function clone(obj) {
   const index = arr.indexOf(obj);
   if (index !== -1) return arr[index];
   // ...
}

这个方案我觉得其实没有太大的问题,但是每次indexOf都会重新遍历数组,而且跟上面的数组方案一样,会影响回收,存在性能问题

方案三:WeakMap

这个方案是我在网上看到的,应该是目前这个问题的最佳解决方案了

function correctClone(obj, map = new WeakMap()) {
  if (map.has(obj)) return map.get(obj); // 查找克隆对象
​
  const clone = {};
  map.set(obj, clone); // 建立原始对象 → 克隆对象的映射
​
  for (const key in obj) {
    clone[key] = correctClone(obj[key], map);
  }
​
  return clone;
}
​

假设克隆一个包含 N 个节点的树形结构,那么WeakMap的查找复杂度仅为O(1)

除了上面的循环引用问题外,还有很多其他的细节,比如一些特殊类型数据的处理等,接下来给大家实现一个完整的深拷贝

function deepClone(target, map = new WeakMap()) {
    // 处理基本类型和 null
    if (target === null || typeof target !== 'object') {
      return target;
    }
  
    // 处理循环引用
    if (map.has(target)) {
      return map.get(target);
    }
  
    // 处理特殊对象类型
    const constructor = target.constructor;
  
    // Date 类型
    if (target instanceof Date) {
      const clone = new Date(target.getTime());
      map.set(target, clone);
      return clone;
    }
  
    // RegExp 类型
    if (target instanceof RegExp) {
      const clone = new RegExp(target.source, target.flags);
      clone.lastIndex = target.lastIndex; // 保持匹配状态
      map.set(target, clone);
      return clone;
    }
  
    // Set 类型
    if (target instanceof Set) {
      const clone = new Set();
      map.set(target, clone);
      target.forEach(value => {
        clone.add(deepClone(value, map));
      });
      return clone;
    }
  
    // Map 类型
    if (target instanceof Map) {
      const clone = new Map();
      map.set(target, clone);
      target.forEach((value, key) => {
        clone.set(deepClone(key, map), deepClone(value, map));
      });
      return clone;
    }
  
    // ArrayBuffer 类型
    if (target instanceof ArrayBuffer) {
      const clone = target.slice(0);
      map.set(target, clone);
      return clone;
    }
  
    // TypedArray 类型 (Uint8Array 等)
    if (ArrayBuffer.isView(target)) {
      const bufferClone = deepClone(target.buffer, map);
      const TypedArrayConstructor = target.constructor;
      return new TypedArrayConstructor(
        bufferClone,
        target.byteOffset,
        target.length
      );
    }
  
    // DataView 类型
    if (target instanceof DataView) {
      const bufferClone = deepClone(target.buffer, map);
      return new DataView(
        bufferClone,
        target.byteOffset,
        target.byteLength
      );
    }
  
    // 处理数组
    if (Array.isArray(target)) {
      const clone = [];
      map.set(target, clone);
      for (let i = 0; i < target.length; i++) {
        clone[i] = deepClone(target[i], map);
      }
      return clone;
    }
  
    // 处理普通对象(保持原型链)
    const clone = Object.create(Object.getPrototypeOf(target));
    map.set(target, clone);
  
    // 处理 Symbol 属性和不可枚举属性
    Reflect.ownKeys(target).forEach(key => {
      clone[key] = deepClone(target[key], map);
    });
  
    return clone;
  }
​

以上就是一个完整的深拷贝,如果还有遗漏的边界情况或者有错误,欢迎各位指正

2. 跨域JSONP的原理

这个问题我没有回答出来,因为我在开发过程中的跨域都用CORS解决了......对jsonp的了解实在是不多,好吧,就没了解过......

首先,同源策略会限制不同源(协议、域名、端口不同)的 AJAX 请求,跨域会报CORS错误。但是这里有个漏洞:<script>标签可以加载任意域的 JavaScript 资源,不受同源策略限制

JSONP 作为早期解决跨域请求的方案,就是利用<script>标签的特性来绕过同源策略,允许跨域获取数据

JSONP的基本流程

  1. 前端创建<script>标签,向目标服务器请求数据,并指定回调函数(callback)。
  2. 服务器返回一个 JavaScript 代码片段,调用前端提供的回调函数,传入数据
  3. 前端执行回调函数,获取数据并处理

为什么后来渐渐不用JSONP了呢

  1. 只能使用 GET 请求:JSONP 依赖<script>标签,无法发送 POST、PUT、DELETE 等请求

  2. 安全性低:

    • 服务器返回的是可执行 JavaScript 代码,可能被利用进行 XSS(跨站脚本攻击)
    • 如果目标服务器被攻击,可能返回恶意代码,执行在前端页面上
  3. 不支持HTTP状态码,无法通过状态码判断成功或者失败,只能判断是否超时

3. 一行五个元素A等宽占满一行,每个元素A中有一个与元素等宽,高度为宽度两倍的图片B,A中还会有其他元素,在拖拽浏览器宽度时,图片B宽度随着拖动进行适配,其他元素不受影响, 用css 如何实现

首先关于高度是宽度的两倍,我第一想到的是css中的宽高比属性aspect-ratio,其次一行五个元素等宽,设置当前行css属性display:flex,元素A设置flex:1,实现等宽且占满一行,然后通过vw去做展示

但是面试官当时问我关于宽高比有没有其他的css实现方法,而且他虽然没说,我感觉他对适配的方案应该也不是特别满意

我就在想,关于宽高比,首先他是一个比例的问题,能不能通过去设置什么属性的比例去实现,回家试了好多次,终于让我找到了padding-top

<!DOCTYPE html>
<html lang="en"><head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    .container {
      display: flex;
​
      /* 让元素均匀分布 */
      justify-content: space-between;
​
      /* 可选的间距 */
      gap: 10px;
      
    }
​
    .item {
      /* 让元素均分容器宽度 */
      flex: 1;
      
      /* 确保一行 5 个 */
      max-width: calc(100% / 5);
      
      position: relative;
    }
​
    .image {
      width: 100%;
      /* 2 倍宽度的高度 */
      height: 0;
      padding-top: 200%;
     
      background: url('1.jpeg') center/cover no-repeat;
    }
  </style>
</head><body>
  <div class="container">
    <div class="item">
      <div class="image"></div>
      <p>其他内容</p>
    </div>
    <div class="item">
      <div class="image"></div>
      <p>其他内容</p>
    </div>
    <div class="item">
      <div class="image"></div>
      <p>其他内容</p>
    </div>
    <div class="item">
      <div class="image"></div>
      <p>其他内容</p>
    </div>
    <div class="item">
      <div class="image"></div>
      <p>其他内容</p>
    </div>
  </div></body></html>

4. 实现一个对话框上的那种小三角,如下图

截屏2025-03-26 21.27.46.png

说实话,我第一反应是让UI给我切个图吧......哎!

这个问题其实我后来思考了一下我回答的应该没什么大问题,但是还是记录一下吧

首先,用css实现一个等腰三角形

.triangle {
  width: 0;
  height: 0;
  border-left: 50px solid transparent;
  border-right: 50px solid transparent;
  border-bottom: 100px solid red; /* 设置三角形的颜色 */
}

将这个小三角放到对话框的外面,实现样式效果

  1. 如果已知宽高的话,可以直接计算出来left的值

  2. 如果宽高不确定的话

    position: absolute; top: -50px; 让三角形定位到矩形顶部。
    left: 50%; transform: translateX(-50%); 让其水平居中
    

差不多就这些了,最后,希望大家都能offer拿到手软!!