最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 十分钟带你吃透js中的“深浅拷贝”

    正文概述 掘金(前端小咖秀)   2021-03-20   457

    说在前面的话

    在日常开发项目中,我们会经常遇到这样的问题,例如: 需要复制一份对象或数组,但是又不能改变原始对象,咋办呢?这时js的深浅拷贝就派上用场了。在JS中,除了基本数据类型,其他一切皆对象。

    想要了解 “深浅拷贝” 我们首先得要了解js的“数据类型” 及 “栈区和堆区”。

    数据类型

    • 基本数据类型 (String, Number, Boolean, null, undefined, Symbol, BigInt)
    • 引用数据类型 (Object, Array, Data, Math, RegExp)

    内存中的栈区(stack)和堆区(heap)

    咱们知道,“基本数据类型”在内存中占有固定大小的空间,通过按值访问。“引用数据类型”在内存中大小不固定,所以其在栈中只存放堆内存地址(内存地址是大小固定的),指向的却是堆中的对象,所以它是按引用访问的。假如需要获取“引用数据类型”中的变量,需要先访问栈内存中的地址,然后根据该地址才能获取到相对应的值。

    基本数据类型(栈区)

    let a = 100;
    let b = a;
    b = 200;
    console.log(a);  // a = 100
    console.log(b);  // b = 200
    注释:基本数据类型是按值存放的,故可以直接 “按值访问”,上述a赋值给b,对b数据做修改后,是不会影响原始数据的值。
    

    引用数据类型(堆区)

    let a = {
        name: 'voledy',
        age: 18
    };
    let b = a;
    b.name = '哇哈哈';
    console.log(a);  // a = {name: '哇哈哈', age: 18}
    console.log(b);  // b = {name: '哇哈哈', age: 18}
    注释:因为a是引用类型,a赋值给b,其实就是将a的内存地址指向b,它们指向的是同一内存地址,所以b的属性改了即a的属性也会跟着变。
    

    浅拷贝和深拷贝

    用最简单的一句话概括:当你在目标对象中修改一个值的时候,看对原对象是否有影响。如果有影响那就是浅拷贝,如果没有那就深拷贝。

    上面说了一大堆,估计你都看的有点烦了,那现在我们就开始进入今天的正题吧!!!

    一 浅拷贝

    1. Object.assign('目标对象', obj1, obj2...)

    //当定义的对象只有基本类型时,该方法就是深拷贝。
    let a = {
        name: 'voledy',
        age: 18
    };
    let b = Object.assign({}, a);
    b.name = "哇哈哈";
    console.log(a);   // a = {name: 'voledy', age: 18};
    console.log(b);   // b = {name: '哇哈哈', age: 18};
    
    //当定义的对象中有引用类型的时,该方法就是浅拷贝。
    let a = {
        name: 'voledy',
        age: 18,
        eat:{
            type: '苹果',
            price: 18,
        }
    };
    let b = Object.assign({}, a);
    b.name = "哇哈哈";
    b.eat.type = "西瓜";
    b.eat.price = 30;
    console.log(a);  // a = {name: 'voledy', age: 18, eat:{ type: '西瓜', price: '30'}};
    console.log(b);  // b = {name: '哇哈哈', age: 18, eat:{ type: '西瓜', price: '30'}};
    

    2. ES6中的扩展运算符

    //我们通过使用扩展运算符(...)也能实现深拷贝。该方法和上述方法一样只能用于深拷贝第一层的值,当拷贝第二层的值时
    仍是引用同一个内存地址。
    let a = {
        name: 'voledy',
        age: 18
    };
    let b = {...a};
    b.name = "哇哈哈";
    console.log(a);   // a = {name: 'voledy', age: 18};
    console.log(b);   // b = {name: '哇哈哈', age: 18};
    
    -----------------------------  分割线  -----------------------------
    
    let a = {
        name: 'voledy',
        age: 18,
        eat:{
            type: '苹果',
            price: 18,
        }
    };
    let b = {...a};
    b.name = "哇哈哈";
    b.eat.type = "西瓜";
    b.eat.price = 30;
    console.log(a);  // a = {name: 'voledy', age: 18, eat:{ type: '西瓜', price: '30'}};
    console.log(b);  // b = {name: '哇哈哈', age: 18, eat:{ type: '西瓜', price: '30'}};
    

    3. 数组中的方法:Array.prototype.slice() 和 Array.prototype.concat()

    //该方法只能用于深拷贝第一层的值,当拷贝第二层的值时仍是引用同一个内存地址。
    ------------------------ 代码就不写了,有兴趣的可以自己去试下... ------------------------
    

    总结
    浅拷贝会在栈中开辟一个新的内存空间,将原对象一级中的“基本数据类型”复制一份到新的内存空间,所以相互不影响。当对象中有“引用类型”时,它只能拷贝“引用类型”在堆内存中的地址,所以赋值后会影响原对象的值。

    二 深拷贝

    1. JSON.parse(JSON.stringify(obj))

    //该方法可以实现深拷贝。 但是需要注意
    1:会忽略undefined,Symbol,函数。
    2:在处理new Date() 会出错
    3:循环引用会出错
    4:不能处理正则,拷贝的是一个空对象
    5:继承的属性会丢失
    一句话概括:可以转成 JSON 格式的对象才能使用这种方法。
    
    let a = {
        name: 'voledy',
        age: 18,
        fn: function(){},
        from: undefined,
        to: Symbol('深圳'),
        nums: /'g'/,
        eat:{
            type: '苹果',
            price: 18,
        }
    };
    let b = JSON.parse(JSON.stringify(a));
    b.name = "哇哈哈";
    b.eat.type = "西瓜";
    b.eat.price = 30;
    console.log(a);  // a = {name: 'voledy', age: 18, nums: /'g'/, fn: function(){}, eat:{ type: '苹果', price: '18'}};
    console.log(b);  // b = {name: '哇哈哈', age: 18, nums: {}, eat:{ type: '西瓜', price: '30'}};
    

    2. 使用递归函数

    //写一个递归函数
    deepCopy = (source) =>{
        const targetObj = source.constructor === Array ? [] : {}; // 先判断是数组还是对象
        for(let keys in source){  // 遍历
            if(source.hasOwnProperty(keys)){
                if(source[keys] && typeof source[keys] === 'object'){ // 如果值是对象,直接递归
                    targetObj[keys] = source[keys].constructor === Array ? [] : {};
                    targetObj[keys] = this.deepCopy(source[keys]);
                }else{ // 如果不是,就直接赋值
                    targetObj[keys] = source[keys];
                }
            }
        }
        return targetObj;
    };
    
    let a = {
        name: 'voledy',
        age: 18,
        eat:{
            type: '苹果',
            price: 18,
            tt:{
                aaa: 1000
            }
        }
    };
    let b = this.deepCopy(a);
    b.eat.type = '西瓜';
    b.eat.tt.aaa = '666666';
    console.log(a) // a = {name: 'voledy', age: 18,  eat:{ type: '苹果', price: '18', tt:{aaa: 1000}}};
    console.log(b) // b = {name: 'voledy', age: 18,  eat:{ type: '西瓜', price: '18', tt:{aaa: 666}}};
    

    3. 使用第三方函数库 lodash

    总结
    深拷贝会在堆中开辟一个新的内存空间,将原对象或数组完整的拷贝一份在新的空间中,两者互不影响,老死而不相往来。


    起源地下载网 » 十分钟带你吃透js中的“深浅拷贝”

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元