前面我们讲过,基本数据类型存放的栈中,引用数据类型存放在堆中,指向引用数据类型的变量保存在栈中,它保存着指向堆中对应引用数据类型的内存地址(对以上问题不了解的朋友可以查看之前的一篇文章),由此看如下代码:
let a = 'a';let b = a;let obj1 = { name:'obj.name'}let obj2 = obj2;复制代码
首先声明了变量a并赋值'a',然后声明b = a,这个时候属于赋值,即创建变量b,并赋值'a';
接下来声明对象obj1并赋值,此时在对中创建了对象{name:'obj.name'},然后声明obj2 = obj1,
这个时候只是把obj1中保存的指向对象{name:'obj.name'}的内存地址赋给了obj2,并不会再创建一个对象{name:'obj.name'}
那么接下来看如下代码:
let xm = { name:'小明', hobby:['足球','篮球']}let xz = xm;console.log(xm);// {// name:'小明',// hobby:['足球','篮球']// }console.log(xz);// {// name:'小明',// hobby:['足球','篮球']// }xz.name = '小张';console.log(xm);// {// name:'小张',// hobby:['足球','篮球']// }console.log(xz);// {// name:'小张',// hobby:['足球','篮球']// }复制代码
因为let xz = xm只是把xm保存的对象的内存地址赋给了xz,所以当通过xz.name = '小张'改变了这个对象的时候,打印xm和xz拿到的都是改变后的对象,那么如何复制一个对象呢?
可能你会想到Object.assign()
Object.assign()
方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。 ---- (MDN)
那我们修改上面的代码如下:
let xm = { name:'小明', hobby:['足球','篮球']}let xz = Object.assign({},xm);console.log(xm);// {// name:'小明',// hobby:['足球','篮球']// }console.log(xz);// {// name:'小明',// hobby:['足球','篮球']// }xz.name = '小张';console.log(xm);// {// name:'小明',// hobby:['足球','篮球']// }console.log(xz);// {// name:'小张',// hobby:['足球','篮球']// }复制代码
可能你会觉得还不错嘛,那我们再修改一下:
let xm = { name:'小明', hobby:['足球','篮球']}let xz = Object.assign({},xm);console.log(xm);// {// name:'小明',// hobby:['足球','篮球']// }console.log(xz);// {// name:'小明',// hobby:['足球','篮球']// }xz.name = '小张';xz.hobby.push('乒乓球');console.log(xm);// {// name:'小明',// hobby:['足球','篮球','乒乓球']// }console.log(xz);// {// name:'小张',// hobby:['足球','篮球','乒乓球']// }复制代码
你会发现,不对啊,我明明只想给xz添加爱好,xm的爱好怎么也多了?
下面我们正式引出浅拷贝和深拷贝
浅拷贝
其实浅拷贝就像我们上面说的变量赋值,当对象的属性值为基本数据类型,那么拷贝的就是基本数据类型的值,如果对象的属性值为引用数据类型,那么拷贝的就是内存地址,上面的代码可以用下图更形象的解释:
所以,类似于Object.assign()属于浅拷贝
深拷贝
理解了浅拷贝,深拷贝的概念就呼之欲出了,所谓深拷贝,即不管对象的属性是基本数据类型还是引用数据类型,都会进行拷贝,所以拷贝前后的两个对象是相互独立,互不影响的。
上面例子实现深拷贝代码如下:
let xm = { name:'小明', hobby:['足球','篮球']}let xz = JSON.parse(JSON.stringify(xm));xz.name = '小张';xz.hobby.push('乒乓球');console.log(xm);// {// name:'小明',// hobby:['足球','篮球']// }console.log(xz);// {// name:'小张',// hobby:['足球','篮球','乒乓球']// }复制代码
JSON.parse(JSON.stringify(obj))存在以下几个问题:
let obj1 = { name:'obj', a:undefined, b:/\d{6}/g, c:function(){ console.log(this) }, d:new Date(), s:Symbol(123)}let obj2 = JSON.parse(JSON.stringify(obj1));console.log(obj2);// {// name: "obj",// b: {},// d: "2019-03-26T13:51:45.158Z"// }复制代码
可见对于undefined,函数,Symbol会直接忽略
对于new Date()转换后结果不正确
对于正则转换为{}
再看一种情况:
let obj1 = { a:{name:'a'}}obj1.b = obj1.a;obj1.b.c = obj1.a;console.log(obj1);let obj2 = (JSON.parse(JSON.stringify(obj1)));console.log(obj2);// Uncaught TypeError: Converting circular structure to JSON复制代码
可见对于循环引用,会报错
下篇文章讲如何手动实现一个深拷贝,有兴趣的朋友欢迎关注
如果有错误或者不严谨的地方,请给予指正,十分感谢!