0基础进大厂,第19天——面试官:请你聊聊拷贝

63 阅读3分钟

引言

看完这篇文章,就不要担心面试官问你拷贝的八股了

什么是深浅拷贝?

基本概念

  • 浅拷贝:只复制对象的第一层属性,如果属性是引用类型,则复制的是引用地址
  • 深拷贝:完全复制整个对象,包括所有嵌套的对象,新对象与原对象完全独立

为什么需要深浅拷贝?

// 原始对象
const original = {
  name: 'John',
  hobbies: ['reading', 'coding']
};

// 浅拷贝
const shallowCopy = {...original};
shallowCopy.hobbies.push('gaming');

console.log(original.hobbies); // ['reading', 'coding', 'gaming'] 被影响了!

// 深拷贝
const deepCopy = JSON.parse(JSON.stringify(original));
deepCopy.hobbies.push('swimming');

console.log(original.hobbies); // ['reading', 'coding'] 不受影响

浅拷贝的实现方式

1. 扩展运算符

const obj = { a: 1, b: { c: 2 } };
const shallowCopy = { ...obj };

2. Object.assign()

const obj = { a: 1, b: { c: 2 } };
const shallowCopy = Object.assign({}, obj);

3. 数组的浅拷贝方法

const arr = [1, 2, { a: 3 }];
const shallowCopy1 = arr.slice();
const shallowCopy2 = [...arr];
const shallowCopy3 = Array.from(arr);

手写浅拷贝函数

function shallowCopy(target) {
  // 基本类型直接返回
  if (typeof target !== 'object' || target === null) {
    return target;
  }
  
  // 判断是数组还是对象
  const copy = Array.isArray(target) ? [] : {};
  
  // 遍历属性
  for (let key in target) {
    // 只复制自身属性(不复制原型链上的)
    if (target.hasOwnProperty(key)) {
      copy[key] = target[key];
    }
  }
  
  return copy;
}

// 测试
const original = { a: 1, b: { c: 2 } };
const copy = shallowCopy(original);
console.log(copy); // { a: 1, b: { c: 2 } }
console.log(copy.b === original.b); // true 证明是浅拷贝

深拷贝的实现方式

1. JSON方法(有局限)

const obj = { a: 1, b: { c: 2 } };
const deepCopy = JSON.parse(JSON.stringify(obj));

局限性

  • 不能处理函数、Symbol、undefined
  • 不能处理循环引用
  • 会丢失Date、RegExp等特殊对象的原型链

2. 第三方库(推荐)

// 使用lodash
const _ = require('lodash');
const deepCopy = _.cloneDeep(obj);

手写深拷贝函数

基础版(不考虑特殊类型)

function deepCopy(target) {
  if (typeof target !== 'object' || target === null) {
    return target;
  }
  
  const copy = Array.isArray(target) ? [] : {};
  
  for (let key in target) {
    if (target.hasOwnProperty(key)) {
      copy[key] = deepCopy(target[key]);
    }
  }
  
  return copy;
}

// 测试
const original = { 
  a: 1, 
  b: { 
    c: 2,
    d: [3, 4]
  } 
};
const copy = deepCopy(original);
console.log(copy.b.d === original.b.d); // false 证明是深拷贝

进阶版(处理特殊类型和循环引用)

function deepCopy(target, map = new WeakMap()) {
  // 基本类型直接返回
  if (typeof target !== 'object' || target === null) {
    return target;
  }
  
  // 处理循环引用
  if (map.has(target)) {
    return map.get(target);
  }
  
  // 处理特殊对象类型
  const constructor = target.constructor;
  if (/^(Date|RegExp|Map|Set)$/i.test(constructor.name)) {
    return new constructor(target);
  }
  
  // 创建新对象并存入map
  const copy = Array.isArray(target) ? [] : {};
  map.set(target, copy);
  
  // 复制Symbol属性
  const symKeys = Object.getOwnPropertySymbols(target);
  if (symKeys.length) {
    symKeys.forEach(symKey => {
      copy[symKey] = deepCopy(target[symKey], map);
    });
  }
  
  // 复制普通属性
  for (let key in target) {
    if (target.hasOwnProperty(key)) {
      copy[key] = deepCopy(target[key], map);
    }
  }
  
  return copy;
}

// 测试
const obj = {
  a: 1,
  b: new Date(),
  c: /abc/,
  d: [1, 2, { e: 3 }],
  [Symbol('f')]: 'symbol'
};
obj.self = obj; // 循环引用

const copy = deepCopy(obj);
console.log(copy);
console.log(copy.self === copy); // true 循环引用处理正确
console.log(copy.b instanceof Date); // true
console.log(copy.c instanceof RegExp); // true

深浅拷贝性能对比

// 测试数据
const data = {
  a: 1,
  b: {
    c: 2,
    d: [3, 4, { e: 5 }]
  }
};

// 浅拷贝性能
console.time('shallow copy');
for (let i = 0; i < 10000; i++) {
  shallowCopy(data);
}
console.timeEnd('shallow copy');

// 深拷贝性能
console.time('deep copy');
for (let i = 0; i < 10000; i++) {
  deepCopy(data);
}
console.timeEnd('deep copy');

// JSON深拷贝性能
console.time('JSON deep copy');
for (let i = 0; i < 10000; i++) {
  JSON.parse(JSON.stringify(data));
}
console.timeEnd('JSON deep copy');

应用场景

使用浅拷贝的场景

  1. 对象结构简单,没有嵌套引用
  2. 需要共享某些引用类型数据
  3. 性能要求高,数据量大的情况

使用深拷贝的场景

  1. 需要完全独立的新对象
  2. 对象结构复杂,有多层嵌套
  3. 需要修改嵌套对象而不影响原对象

总结

  1. 浅拷贝只复制第一层,深拷贝复制所有层级
  2. 手写浅拷贝相对简单,深拷贝需要考虑多种边界情况
  3. JSON方法实现深拷贝有局限性,特殊类型和循环引用会出问题
  4. 实际开发中,复杂场景推荐使用成熟的工具库如lodash的cloneDeep
  5. 根据实际需求选择拷贝方式,避免不必要的性能开销