深克隆和浅克隆的几种实现方案

365 阅读3分钟

克隆对象在业务开发中经常遇到,特别是一些底层库的封装过程中。本文介绍浅克隆和深克隆和对应的的实现方案。

什么是浅克隆

下面是浅克隆的一个实现案例:


/**
 * 实现方案一
 * @param {*} obj 
 */
function simpleClone(obj) {
  var newObj = {};
  for(var key in obj){
    console.log('key:', key, obj[key]);
    if(!obj.hasOwnProperty(key)){
      break;
    }
    newObj[key] = obj[key];
  }

  return newObj;
}

/**
 * 实现方案二   用es6中的...
 */
function simpleClone2(obj){
  return {...obj}
}



var obj = {
  a:1,
  b:'ssssss',
  c: {
    x:5,
    y:{
      g:8
    }
  }
}

var newObj = simpleClone2(obj)


console.log('obj', obj);
console.log('newObj', newObj);

console.log(obj === newObj);   //false 
console.log(obj.c === newObj.c);   //true 说明没有克隆成功 都用的同一个引用

浅拷贝除了上面的两种实现方案外,使用Object.assign()也是很常用的一种方案。

let obj1 = { person: {name: "kobe", age: 41},sports:'basketball' };
let obj2 = Object.assign({}, obj1);
obj2.person.name = "wade";
obj2.sports = 'football'
console.log(obj1); // { person: { name: 'wade', age: 41 }, sports: 'basketball' }

发现只有对象的最外层克隆了,属性如果是对象类型,克隆后指向的还是同一个引用。

这个时候就需要深克隆登场了

实现一个深克隆

简单方案:

/**
 * 简单深度克隆
 */
function deepClone(obj){
  return JSON.parse(JSON.stringify(obj))
}

如此对正则、函数、new类型的属性会被忽略掉,如果对象中有这些类型时,该方案是不可行的

/**
 * 
 * 完善的深克隆
 */

function deepClone(obj){
  if(typeof obj !== 'object'){
    return obj;
  }

  if(obj === null){
    return obj;
  }

  if(obj instanceof RegExp){
    //是正则
    return new RegExp(obj);
  }

  if(obj instanceof Date){
    return new Date(obj);
  }

  if(obj instanceof Function){
    return new Function(obj)
  }

  var newObj = {};

  for(var key in obj){

    if(obj.hasOwnProperty(key)){
      newObj[key] = deepClone(obj[key]);
    }
  }

  return newObj;
}


var obj = {
  a:1,
  b:'ssssss',
  c: {
    x:5,
    y:{
      g:8
    }
  },
  d: /ss$/,
  e: new Date(),
  f: function(){
    console.log(88888);
  }
}

var newObj = deepClone(obj)

console.log('obj', obj);
console.log('newObj', newObj);

console.log(obj === newObj);   //false
console.log(obj.c === newObj.c);   //false

此时可能还有一种场景,如果要进行深拷贝的对象属性存在循环引用呢?对象的循环引用,即对象的属性直接引用了自身的情况。解决这个问题,可以额外开辟一个空间来存储,存储当前对象和引用对象的对应关系,当需要拷贝当前对象时,先去存储空间找,如果找到了就直接返回,如果没有就继续拷贝,并记录下来。

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;
}
let obj = { name: 1, address: { x: 100 } };
obj.o = obj; // 对象存在循环引用的情况
let d = deepClone(obj);
obj.address.x = 200;
console.log(d);

参考资料

深拷贝与浅拷贝
致敬ConardLi大佬

总结

本文对比了浅拷贝和深拷贝,并介绍了浅拷贝和深拷常见的几种实现方案。如果对你有帮助,可以给作者点赞加关注呀。迎着风的方向。