赋值、浅拷贝、深拷贝的区别

2020-07-16 17:09:33 浏览数 (1)

数据类型存储

基本类型数据保存在在栈内存中 引用类型数据保存在堆内存中,引用数据类型的变量是一个指向堆内存中实际对象的引用,存在栈中。

为什么基本数据类型存在栈内存,引用数据类型存在堆内存?

  • 基本数据类型比较稳当,相对来说占用的内存较小
  • 引用数据类型是动态的,大小不固定,占用的内存较大,但内存地址大小是固定的,因此可以将内存地址保存在栈中

浅拷贝和赋值(=)的区别

基本类型赋值,系统会为新的变量在栈内存中分配一个新值,这个很好理解。引用类型赋值,系统会为新的变量在栈内存中分配一个值,这个值仅仅是指向同一个对象的引用,和原对象指向的都是堆内存中的同一个对象。

代码语言:javascript复制
var obj1 = new Object();
var obj2 = obj1;
obj1.name = 'lucyStar';
console.log(obj2.name);
// lucyStar

我们可以看到,obj1保存了一个对象的实例,这个值被赋值到 Obj2中。赋值操作完成后,两个变量实际引用的是同一个对象,改变了其中一个,会影响另外一个值。

什么是浅拷贝?如果是对象类型,则只拷贝一层,如果对象的属性又是一个对象,那么此时拷贝的就是此属性的引用。

简单实现一个浅拷贝

代码语言:javascript复制
function shadowCopy(obj) {
    const newObj = {};
    for(let prop in obj) {
        if(obj.hasOwnProperty(prop)){
            newObj[prop] = obj[prop];
        }
    }
    return newObj;
}
const obj1 = {
    name: 'litterStar',
    a: {
        b: '1'
    }
};
const obj2 = shadowCopy(obj1);
obj2.name = 'lucyStar';
obj2.a.b = '2';
console.log(obj1);
// { name: 'litterStar', a: { b: '2' } }
console.log(obj2);
// { name: 'lucyStar', a: { b: '2' } }

可以看到修改obj2name 属性不会影响 obj1,但是修改 obj2的 a属性(是个对象)的 b,就会影响 obj1.a.b

使用下面这些函数得到的都是浅拷贝:

  • Object.assign
  • Array.prototype.slice(), Array.prototype.concat()
  • 使用拓展运算符实现的复制

深拷贝

什么是深拷贝?浅拷贝是只拷贝一层,深拷贝会拷贝所有的属性。深拷贝前后两个对象互不影响。

深拷贝的实现

  • JSON.parse(JSON.stringify())
  • 手写递归函数
  • 函数库lodash

JSON.parse(JSON.stringify())有存在以下问题:

  • 无法解决循环引用问题
  • 无法拷贝特殊的对象,比如:RegExp, Date, Set, Map等在序列化的时候会丢失。
  • 无法拷贝函数

尝试自己写一个深拷贝,需要考虑下面这几种情况

  1. 属性是基本类型
  2. 属性是对象
  3. 属性是数组
  4. 循环引用的情况,比如 obj.prop1 = obj
代码语言:javascript复制
function deepCopy(originObj, map = new WeakMap()) {
    // 判断是否为基本数据类型
    if(typeof originObj === 'object') {
        // 判断是否为数组
        const cloneObj = Array.isArray(originObj) ? [] : {};
        // 判断是否为循环引用
        if(map.get(originObj)) {
            return map.get(originObj);
        }
        map.set(originObj, cloneObj);
        for(const prop in originObj) {
            cloneObj[prop] = deepCopy(originObj[prop], map);
        }
        return cloneObj;
    } else {
        return originObj;
    }
}

const obj1 = {
    a: '111',
}
obj1.obj2 = obj1;

const aa = deepCopy(obj1);
console.log(aa);
// { a: '111', obj2: [Circular] }

上面只是实现一个简单的深拷贝,很多情况未考虑到,比如 特殊的数据类型及兼容性的处理,更多细节的实现可以参考 lodash 中的 cloneDeep[1]方法。

总结

和原数据是否指向同一对象

第一层数据为基本数据类型

原数据中包含对象

赋值

改变会使原数据一同改变

改变会使原数据一同改变

浅拷贝

改变不会使原数据一同改变

改变会使原数据一同改变

深拷贝

改变不会使原数据一同改变

改变不会使原数据一同改变

备注:markdown 表格在 https://tableconvert.com/ 该网站生成,很方便。

参考

  • 如何写出一个惊艳面试官的深拷贝?[2]
  • JavaScript数据类型的存储方法详解[3]

参考资料

[1]cloneDeep: https://github.com/lodash/lodash/blob/master/cloneDeep.js

[2]如何写出一个惊艳面试官的深拷贝?: https://juejin.im/post/5d6aa4f96fb9a06b112ad5b1#heading-5

[3]JavaScript数据类型的存储方法详解: https://www.jb51.net/article/122044.htm

0 人点赞