[合格中级前端开发必知]聊聊深拷贝浅拷贝

·  阅读 2268
[合格中级前端开发必知]聊聊深拷贝浅拷贝

前言

js的深拷贝,浅拷贝问题在项目的开发过程中是频繁出现的。对于一个合格的前端开发者来说,对于深刻理解它们是必要的,并能合理使用深拷贝浅拷贝处理问题。

什么是深拷贝,什么是浅拷贝

以下是我对于它们的理解:

深拷贝:如果是对象a和对象b它们是深拷贝,那么它们不再引用统一内存地址了,也就是它们 老死不相往来了。

浅拷贝:对象a只是复制指向对象b的指针,而不复制对象本身。新旧对象它们还是共享同一内存。

好了,在脑海里大致知道有这么一个小的概念之后,我们来聊聊更深入的内存。

javascript的堆栈

堆&栈 两者都是存放临时数据的地方。

栈是先进后出的,就像一个桶,后进去的先出来,它下面本来有的东西要等其他出来之后才能出来。

堆是在程序运行时,而不是在程序编译时,申请某个大小的内存空间。即动态分配内存,对其访问和对一般内存的访问没有区别。对于堆,我们可以随心所欲的进行增加变量和删除变量,不用遵循次序。

栈区(stack) 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。 堆区(heap) 一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收。 堆(数据结构):堆可以被看成是一棵树,如:堆排序; 栈(数据结构):一种先进后出的数据结构。

数据类型访问&&复制

** 基本数据类型:定义在栈中的,从栈当中直接存取变量的值。** 例如:var a =1;

栈内存
a1

** 基本数据类型复制:复制时,会在栈中创建一个新值,然后把值复制到为新变量分配的位置上。**

例如:var b=a;

栈内存
a1
b1

** 引用数据类型:定义在栈中的,但它的值指向堆当中的一个地址。** 例如:

let obj=Object.create(null);
obj={a:1};
复制代码

** 引用数据类型复制:复制的是存储在栈中的指针,将指针复制到栈中并且为新变量分配空间。而这个指针副本和原指针指向存储在堆中的同一个对象。复制操作结束后,两个变量实际上将引用同一个对象。因此,在使用时,改变其中的一个变量的值,将影响另一个变量。** 例如:

let obj=Object.create(null);
obj={a:1};
let obj2=obj;
复制代码

好了,了解了数据结构之后,那我们来谈谈如何实现深浅拷贝。

如何实现深浅拷贝

毋庸置疑,浅拷贝的实现是大多数人都能想到的,因为在项目的开发的过程当中,就是有时候因为数据浅拷贝而引发一系列的数据问题。

源数据data:

let data={
    name:'ddd',
    type:'es',
    impl:[
        {
            e:1,
            a:2
        },
        {
            e:2,
            a:3
        }
    ]
}
复制代码

如何实现浅拷贝

  • 第一种情况:将数据源data直接赋值给另外一个对象。 猜猜下面代码结果
let obj=data;           //浅拷贝
obj.name='aa'           //当obj修改name的值之后,data的name值也发生改变
console.log(obj1,data); 
复制代码
  • 第二种情况:将数据源data做为另一个对象的子对象。猜猜下面代码结果

let obj1={data};        //定义obj1是一个对象,并且将data是obj1的子对象。
obj1.name='bb'          //给obj1添加name属性,data的name值不发生改变
obj1.data.impl[0].e=4;  //但是修改obj的data属性的值,数据源data会发生改变
obj1.data.name='pp';
console.log(obj1,data);
复制代码
  • 第三种情况:使用es6的扩展符号{...XXX}。猜猜下面代码结果
let obj2={...data};     
obj2.name='cc';
obj2.impl[0].e=3
console.log(obj2,data);
复制代码

第三种的情况是特殊的,但是这种在JavaScript当中是常见的。有时候是深拷贝,有时候是浅拷贝,只有当第一层属性值不是对象的时候则是深拷贝否则就是浅拷贝。 总结一下有时候深拷贝有时候浅拷贝的特殊语法:

1、for in 遍历循环拷贝

2、es6 Object.assign({ },obj)

3、{… obj}

4、concat

5、slice

好吧,上面三种情况的结果依次如下:

第一种情况: 第二种情况: 第三种情况:

那如何才能实现真正的拷贝呢?也就是真的老死不相往来了,一点关系也没有了呢?

如何实现深拷贝

  • 利用JSON对象的parse和stringfy

JSON.stringfy是将一个js对象值转成一个JSON字符串,JSON.parse是将一个JSON字符串转成一个js对象。先转成字符串再转对象,所生成的对象和原来的对象不共用同一内存,实现深拷贝的过程。

但是有个问题:undefined、function、symbol在转化中会被忽略。

后记:使用JSON.stringfy(参数1,参数2,参数3)进行过滤。

 var foo = {
            bar: "1",
            baz: "3",
            o: {
                name: 'xiaolo',
                age: 21,
                info: {
                    sex: 'nan',
                    getSex: function() {
                        return 'nan';
                    }
                }
            }
        }
        var f = JSON.stringify(foo, cencer);
        var c = JSON.parse(f);
        console.log(c);
复制代码

如下图:

  • 利用递归函数来实现

实现思路就是循环对象,判断对象的属性值是否还是对象,如果还是那就递归。

function cloneData(obj){
    let target;
    if(typeof obj==='object' && obj!==null){
        if(Array.isArray(obj)){
            target=[];
        }else{
            target={};
        }
    }else{
        return target=obj;
    }
    for(let key in obj){
        if(obj.hasOwnProperty(key)){
            if(typeof obj[key] ==='object'){
                target[key]=cloneData(obj[key]);
            }else{
                target[key]=obj[key];
            }
        }
    }
    return target;
}

复制代码

补充:上面的cloneData对于对象环是无法拷贝的,会栈溢出。所以考虑到对象环的问题需要修改cloneData的函数。

function checktype(obj) {
  //检查对象类型
  return Object.prototype.toString.call(obj).slice(8, -1)
}
function depCopy(target, hash = new WeakMap()) {
  //hash 作为一个检查器,避免对象深拷贝中出现环引用,导致爆栈。
  let type = checktype(target)
  let result = null
  if (type == 'Object') {
    result = {}
  } else if (type == 'Array') {
    result = []
  } else {
    return target
  }
  if (hash.has(target)) {
    //检查是有存在相同的对象在之前拷贝过,有则返回之前拷贝后存于hash中的对象
    return hash.get(target)
  }
  hash.set(target, result) //备份存在hash中,result目前是空对象、数组。后面会对属性进行追加,这里存的值是对象的栈
  for (let i in target) {
    if (
      checktype(target[i]) == 'Object' ||
      checktype(target[i]) == 'Array'
    ) {
      result[i] = depCopy(target[i], hash) //属性值是对象,进行递归深拷贝
    } else {
      result[i] = target[i] //其他类型直接拷贝
    }
  }
  return result
}
复制代码
  • loadsh第三方库来实现 _.cloneDeep

引入第三方库,使用现成api实现深拷贝。

var obj={
        a:{
            b:2,
            c:3
        }
    }
    var obj1=_.cloneDeep(obj);
    obj1.a.c=6;
复制代码

总结

以上就是我对深拷贝浅拷贝的理解,小伙伴可以自己动手一下,如果上述的内容有帮到你,那就给我点个赞哈~感谢啦!!如有其他的想法可在评论区留言,互相学习成长~~

分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改