Skip to content

深拷贝与浅拷贝

如果属性是基本类型,拷贝的就是基本类型的值。如果属性是引用类型,拷贝的就是内存地址,浅拷贝后的内容的内存地址相同

即浅拷贝是拷贝一层,深层次的引用类型则共享内存地址

js
function shallowClone(obj) {
    const newObj = {};
    for(let prop in obj) {
        if(obj.hasOwnProperty(prop)){
            newObj[prop] = obj[prop];
        }
    }
    return newObj;
}

JavaScript中,存在浅拷贝的现象有:

方法示例适用场景
Object.assign()const copy = Object.assign({}, obj)简单对象合并,复制可枚举自有属性
展开运算符 ...const copy = { ...obj }
const arrCopy = [...arr]
对象或数组的快速浅拷贝
Array.prototype.slice()const arrCopy = arr.slice()数组浅拷贝
Array.prototype.concat()const arrCopy = [].concat(arr)数组浅拷贝
手动遍历赋值const copy = {};
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
copy[key] = obj[key];
}
}
需要过滤原型链属性的场景

深拷贝开辟一个新的栈,两个对象属性完全相同,但是对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性

常见的深拷贝方式有:

方法示例优缺点
JSON.parse(JSON.stringify())const copy = JSON.parse(JSON.stringify(obj))优点:简单
缺点:丢失函数、Symbol、undefined、循环引用报错
递归实现见下方完整代码优点:可定制处理特殊类型
缺点:需处理复杂边界
_.cloneDeep(Lodash)const copy = _.cloneDeep(obj)优点:功能完善
缺点:需引入第三方库
structuredClone(现代API)const copy = structuredClone(obj)优点:原生支持,处理循环引用
缺点:不兼容IE,无法克隆函数、DOM
jQuery.extend()

手写深拷贝

js
function deepClone(obj, hash = new WeakMap()) {
  if (obj === null) return obj; // 如果是null或者undefined我就不进行拷贝操作
  if (obj instanceof Date) return new Date(obj);
  if (obj instanceof RegExp) return new RegExp(obj);
  // 可能是对象或者普通的值  如果是函数的话是不需要深拷贝
  if (typeof obj !== "object") return obj;
  // 是对象的话就要进行深拷贝
  if (hash.get(obj)) return hash.get(obj);
  let cloneObj = new obj.constructor();
  // 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
  hash.set(obj, cloneObj);
  for (let key in obj) {
    if (obj.hasOwnProperty(key)) {
      // 实现一个递归拷贝
      cloneObj[key] = deepClone(obj[key], hash);
    }
  }
  return cloneObj;
}
js
function deepClone(target, map = new WeakMap()) {
  // 处理基本类型、函数(直接返回)
  if (typeof target !== 'object' || target === null) return target;

  // 处理循环引用:已拷贝过的对象直接返回
  if (map.has(target)) return map.get(target);

  // 处理特殊对象类型
  const constructor = target.constructor;
  let clone;
  switch (constructor) {
    case Date:
      clone = new Date(target.getTime());
      break;
    case RegExp:
      clone = new RegExp(target.source, target.flags);
      break;
    case Map:
      clone = new Map();
      target.forEach((v, k) => clone.set(deepClone(k, map), deepClone(v, map)));
      break;
    case Set:
      clone = new Set();
      target.forEach(v => clone.add(deepClone(v, map)));
      break;
    default:
      // 继承原型链
      clone = Object.create(Object.getPrototypeOf(target));
  }

  // 缓存当前对象,防止循环引用
  map.set(target, clone);

  // 处理普通对象和数组
  Reflect.ownKeys(target).forEach(key => {
    clone[key] = deepClone(target[key], map);
  });

  return clone;
}