我们都知道,在JS中对象属于引用类型,在内存中是以地址的形式保存数据。那么当我们需要复制/克隆一个对象的时候,克隆的仅是内存地址,这个地址实际指向的才是真正的数据。改变两个对象中的任何一个,另一个对象也会跟着改变。其实这种问题在实际项目中也是经常出现的。实际项目中可能需要操作一个克隆的新对象而非老对象,我一般习惯直接用JSON.parse(JSON.stringfy())来克隆一个对象。这样做属于深克隆,就是复制出来的对象和原对象没有任何关系。虽然简单,但是这种方式也有缺点,有什么缺点呢,一起来探究下JS中的深浅克隆


浅克隆
对于一个多维的对象 只克隆了第一维度

比如说现在有这样一个对象obj:

let obj = {
      a: 100,
      b: [10, 20, 30],
      c: {
        x: 10
      },
      d: /^\d+$/,
      e: new Date(),
      f: function () {
        console.log('fff')
      }
    };
浅克隆方法1
我们写一个浅克隆的方法 cloneObj
// 方法1:
function cloneObj(obj){
    let obj2 = {};
    for(let key in obj){
        // 循环原对象中的每个key
        if(!obj.hasOwnProperty(key)) break;
    
        obj2[key] = obj[key];
    }
    return obj2;
}
hasOwnProperty()方法会去查找obj上是否有某属性,返回的是布尔值 但是不会去原型链上去查找。上面for循环中就是通过该原理来中断循环
可以测试一下该方法是否可用
let result = cloneObj(obj);
console.log(result);
obj.a = 200;
console.log(result);
打印结果:
{
  a: 100,
  b: [ 10, 20, 30 ],
  c: { x: 10 },
  d: /^\d+$/,
  e: 2020-02-18T12:03:49.133Z,
  f: [Function: f]
}
{
  a: 100,
  b: [ 10, 20, 30 ],
  c: { x: 10 },
  d: /^\d+$/,
  e: 2020-02-18T12:03:49.133Z,
  f: [Function: f]
}

可见,新复制的对象属性值并没有因为旧对象属性值的改变而改变。貌似没有问题,那如果改变旧对象的第二层属性呢?

obj.c.x = 100000;
console.log(result);
打印结果:
{
  a: 100,
  b: [ 10, 20, 30 ],
  c: { x: 100000 }, // c 也会跟着改变
  d: /^\d+$/,
  e: 2020-02-18T12:03:49.133Z,
  f: [Function: f]
}

总结 该方法可以实现克隆,但是只能实现浅克隆
除了这个方法我们该可以通过以下更简单方式来实现浅克隆:

浅克隆方法2
展开运算符
// 方法2
let new_obj = { ...obj }
浅克隆方法3
Object.assign()
Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)
// 方法3
let new_obj = Object.assign({},obj);

深克隆
知道了浅克隆 那么对应的深克隆自然而然也就知道
深克隆就是对于一个多维的对象 克隆了多个维度
深克隆方法1
JSON.parse(JSON.stringfy())
相信这是很多人在实际项目中用到的深克隆方式
let new_obj = JSON.parse(JSON.stringify(obj));
测试一下该方法是否可用
console.log(new_obj);
obj.a = 200;
obj.c.x = 100000;
console.log(new_obj);
打印结果:
{
  a: 100,
  b: [ 10, 20, 30 ],
  c: { x: 10 },
  d: {},
  e: '2020-02-19T06:17:56.741Z'
}
{
  a: 100,
  b: [ 10, 20, 30 ],
  c: { x: 10 },
  d: {},
  e: '2020-02-19T06:17:56.741Z'
}
可以发现 不管是改变原数组中一维属性obj.a = 200还是二维属性obj.c.x = 100000,新数组的属性都没有跟着改变。但是同时也发现了一个问题: 一些属性在克隆过程中消失/变化了。
1 - 最明显的是函数类型在克隆过程中消失了
2 - 原对象中的正则在新对象中变成了一个空对象
3 - 而且时间类型数据被转成了一个字符串类型,而不是一个时间对象
所以说该方法在对于函数 / 正则 / 时间类型的克隆都有问题。
对于原对象中只包含一般属性的克隆 我们可以使用JSON.parse(JSON.stringfy())方法来实现
深克隆方法2
写一个递归方法来实现深克隆
function deepClone(obj){
    // null情况
    if(obj === null) return null;
    
    // 基本类型
    if(typeof obj !== 'object') return obj;
    
    // 函数类型
    if(obj instanceof Function) return new Function(obj);
    
    // 正则类型
    if(obj instanceof RegExp) return new RegExp(obj);
    
    // 时间类型
    if(obj instanceof Date) return new Date(obj);
    
    
    // 通过原对象obj的构造函数再new一个新的实例
    let new_obj = new obj.constructor();
    
    // 遍历
    for(let key in obj){
        // 只克隆原对象obj的私有属性 不克隆原对象obj的原型链上的属性
        if(obj.hasOwnProperty(key)){
            new_obj[key] = deepClone(obj[key])
        }
    }
    return new_obj
}
测试一下该方法是否可用
let result = deepClone(obj);
console.log(result);
obj.a = 200;
obj.c.x = 100000;
console.log(result);
打印结果:
{
  a: 100,
  b: [ 10, 20, 30 ],
  c: { x: 10 },
  d: /^\d+$/,
  e: 2020-02-19T06:37:55.756Z,
  f: [Function: f]
}
{
  a: 100,
  b: [ 10, 20, 30 ],
  c: { x: 10 },
  d: /^\d+$/,
  e: 2020-02-19T06:37:55.756Z,
  f: [Function: f]
}

即可以看到,不管是函数类型 正则类型 还是时间类型 都得到了正确的深克隆,而且更改原对象的不管是几维属性新对象都不会受到影响。


Funky_Tiger
443 声望33 粉丝

刷题,交流,offer,内推,涨薪,相亲,前端资源共享...