深拷贝

2020-08-07  本文已影响0人  达文西_Huong

深拷贝

前言

写在前面,前几天面试的时候,被问到深拷贝有什么方式实现的时候。脑袋居然一空,只记得可以转成字符串和递归,但是具体的核心原理居然一点也想不起来了。所以为了让自己想起来,我决定重新查阅文章,并且自己编写一篇文章来加深记忆。

以下的描述为阅读了网上的文章之后结合自己的理解去描述的,如果有表达不清晰或者写得不正确的地方,还请见谅,以后我会随着自己的深入学习,继续回来修改这篇文章。

基本数据类型 和 引用数据类型

拷贝,其实就是字面意思,当我们为了复制一个变量的值到另一个变量的一种行为。不过这里会存在两种情况。

我们都知道数据类型分为,基本数据类型引用数据类型。基本数据类型指的就是 String,Boolean,null,undefined,Number

基本数据类型一般是存储在栈内存中,复制的时候是值得直接传递。例如说:

    var a = 123
    var b = a

那么这个时候,会在栈内存中新分配一个空间,然后把value复制给新得变量。
如下图:

image

下面我们再来介绍一下引用数据类型

引用数据类型,其实就是数据会存放再堆内存中的。而在栈中的变量存放的是指向堆内存的地址。

而如果你想复制一个变量,给另一个变量,其实复制的只是引用地址。

    var a = new Object()
    var b = a 

如下图是复制后的效果

image

深拷贝和浅拷贝

知道了上面的这部分基础知识之后,下面就很好理解了。所谓的浅拷贝其实就是仅仅复制的是地址。而所谓的深拷贝,其实就是不仅仅复制的是引用地址,而是复制堆内存中的对象本身。

深拷贝完之后,修改原本的对象数据的时候,新复制出来的数据是不会发生变化的。

浅拷贝很简单,就不需要再说了,下面就直接上深拷贝的实现方式

通过递归的方法去实现
    function deepClone(obj) {
        var target = {}
        for(var key in obj) {
            // 过滤掉原型链上的可遍历的数据(即只复制自身的属性)
            if(Object.peototype.hasOwnProperty.call(obj, key)){
                if(typeof obj[key] === 'object'){
                    target[key] = deepClone(obj[key])
                } else {
                    target[key] = obj[key]
                }
            }
        }
        return target
    }
通过字符串的方式去实现
    function(obj) {
        // 其实就是把对象转成字符串(即把引用数据类型转成基本数据类型)
        let strObj = JSON.stringify(obj)
        let copyObj = JSON.parse(strObj)
        return copyObj
    }
通过Objcet.create() 实现
    function deepCopy(obj) {
        // 创建一个对象,并且给这个对象绑定一个原型
        // getPrototypeOf 获取原型
        var copy = Object.create(Object.getPrototypeOf(obj))
        // 遍历获取自身的属性(包含不可遍历)----并且不会从原型链上遍历
        var propNames = Objcet.getOwnPropertyNames(obj)

        propNames.forEach(function(name) {
            // 获取每一个属性的 描述 (即是否可遍历,能够执行delet,能否修改值)
            var desc = Object.getOwnPropertyDescriptor(obj, name)
            // 给一个目标对象添加一个属性,以及属性描述
            Object.defineProperty(copy, name, desc) 
        })

        return copy
    }
补充一个递归的方法
  function deepClone( obj, hash = new WeakMap() ){
    // 如果是 null 或者 undefined 我就不进行拷贝操作了
    if(obj === null ) return obj

    if(obj instanceof Date ) return new Date( obj )

    if(obj instanceof RegExp) return new RegExp(obj)

    // obj 可能是对象或者普通值,如果只是个函数的话,也不进行拷贝
    if(typeof obj !== "object") return obj

    // 是对象的话就要进行拷贝了
    if(hash.get(obj)) return hash.get(obj)

    // 找到的是所属类原型上的 constructor,而原型上的 construction 指向的是当前类的本身
    let cloneObj = new obj.constructor()

    hash.set(obj, cloneObj);

    for(let key in obj) {
        if(obj.hasOwnProperty(key)) {
            // 实现一个递归拷贝
            cloneObj[key] = deepClone(obj[key], hash)
        }
    }
    return cloneObj
    
  }

  // test

  let obj = {
      name:1,
      address:{
          x:100
      }
  }

  obj.o = obj   // 对象存在循环引用的情况
  let d = deepClone(obj)
  obj.address.x = 200
  console.log(d)```

总结

文章到这里就结束了,其实深拷贝的难点就在于一开始我们提到的。由于栈内存中存储的变量的值存储的是引用地址,而你想复制,只能通过把堆内存中的值遍历出来,然后重新添加,并且重新分配出新的堆内存和新的引用地址。


以上

参考文章: https://segmentfault.com/a/1190000018371840

上一篇 下一篇

猜你喜欢

热点阅读