Skip to content

JavaScript 数据操作与优化面试题

1. 请解释 JavaScript 中的深拷贝和浅拷贝

Details

在 JavaScript 中,拷贝是指创建一个新对象,使其包含与原对象相同的值。根据拷贝的深度,可分为浅拷贝和深拷贝。

浅拷贝

浅拷贝是指创建一个新对象,新对象的属性值与原对象相同,但对于引用类型的属性,新对象和原对象共享同一个引用。

浅拷贝的方法

  1. Object.assign()

    javascript
    const obj = { a: 1, b: { c: 2 } };
    const copy = Object.assign({}, obj);
    copy.b.c = 3;
    console.log(obj.b.c); // 输出: 3(原对象也被修改)
  2. 展开运算符 (...)

    javascript
    const obj = { a: 1, b: { c: 2 } };
    const copy = { ...obj };
    copy.b.c = 3;
    console.log(obj.b.c); // 输出: 3(原对象也被修改)
  3. Array.prototype.slice()(用于数组):

    javascript
    const arr = [1, { a: 2 }];
    const copy = arr.slice();
    copy[1].a = 3;
    console.log(arr[1].a); // 输出: 3(原数组也被修改)
  4. Array.prototype.concat()(用于数组):

    javascript
    const arr = [1, { a: 2 }];
    const copy = [].concat(arr);
    copy[1].a = 3;
    console.log(arr[1].a); // 输出: 3(原数组也被修改)

深拷贝

深拷贝是指创建一个新对象,新对象的属性值与原对象相同,对于引用类型的属性,新对象会创建一个新的引用,而不是共享原对象的引用。

深拷贝的方法

  1. JSON.parse(JSON.stringify())

    javascript
    const obj = { a: 1, b: { c: 2 } };
    const copy = JSON.parse(JSON.stringify(obj));
    copy.b.c = 3;
    console.log(obj.b.c); // 输出: 2(原对象未被修改)

    注意:这种方法有一些限制,比如不能处理函数、undefined、Symbol 等类型。

  2. 递归实现深拷贝

    javascript
    function deepClone(obj) {
      if (obj === null || typeof obj !== 'object') {
        return obj;
      }
      if (obj instanceof Date) {
        return new Date(obj.getTime());
      }
      if (obj instanceof Array) {
        return obj.map(item => deepClone(item));
      }
      if (typeof obj === 'object') {
        const clonedObj = {};
        for (const key in obj) {
          if (obj.hasOwnProperty(key)) {
            clonedObj[key] = deepClone(obj[key]);
          }
        }
        return clonedObj;
      }
    }
  3. 使用第三方库:如 Lodash 的 _.cloneDeep() 方法。

浅拷贝 vs 深拷贝

特性浅拷贝深拷贝
拷贝层级只拷贝一层拷贝所有层级
引用类型共享引用创建新引用
性能更快更慢
内存占用更少更多

应用场景

  • 浅拷贝:适用于只包含基本数据类型的对象,或者不需要修改引用类型属性的情况。
  • 深拷贝:适用于包含多层嵌套引用类型的对象,或者需要修改拷贝对象而不影响原对象的情况。

2. 请解释 JavaScript 中的防抖(Debounce)和节流(Throttle)

Details

防抖和节流是 JavaScript 中用于控制函数执行频率的两种技术,它们可以优化性能,特别是在处理频繁触发的事件时。

防抖(Debounce)

防抖是指在一段时间内,如果事件被多次触发,只执行最后一次触发的事件。

防抖的实现

javascript
function debounce(func, delay) {
  let timeoutId;
  return function(...args) {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => {
      func.apply(this, args);
    }, delay);
  }
}

防抖的应用场景

  1. 搜索框输入:用户输入时,不需要每次按键都发送请求,而是等待用户停止输入一段时间后再发送。
  2. 窗口 resize:窗口大小改变时,不需要每次改变都执行 resize 事件处理函数,而是等待调整完成后再执行。
  3. 表单提交:防止用户重复点击提交按钮。

防抖的示例

javascript
const debouncedSearch = debounce((query) => {
  console.log('Searching for:', query);
  // 发送搜索请求
}, 300);

// 监听输入事件
document.getElementById('search').addEventListener('input', (e) => {
  debouncedSearch(e.target.value);
});

节流(Throttle)

节流是指在一段时间内,无论事件被触发多少次,只执行一次事件。

节流的实现

javascript
function throttle(func, delay) {
  let lastExecTime = 0;
  return function(...args) {
    const currentTime = Date.now();
    if (currentTime - lastExecTime >= delay) {
      lastExecTime = currentTime;
      func.apply(this, args);
    }
  }
}

节流的应用场景

  1. 滚动事件:滚动时,不需要每次滚动都执行事件处理函数,而是每隔一段时间执行一次。
  2. 游戏中的动画:游戏中的动画更新不需要每一帧都执行,而是每隔一段时间执行一次。
  3. 鼠标移动事件:鼠标移动时,不需要每次移动都执行事件处理函数,而是每隔一段时间执行一次。

节流的示例

javascript
const throttledScroll = throttle(() => {
  console.log('Scroll position:', window.scrollY);
  // 处理滚动事件
}, 100);

// 监听滚动事件
window.addEventListener('scroll', throttledScroll);

防抖 vs 节流

特性防抖节流
执行时机事件停止触发后延迟执行每隔一段时间执行一次
执行次数最后一次触发时执行一次多次触发时每隔一段时间执行一次
应用场景搜索输入、窗口 resize滚动事件、游戏动画

总结

防抖和节流都是用于优化性能的技术,它们可以减少函数的执行次数,提高应用的响应速度。选择使用哪种技术取决于具体的应用场景。