在使用JavaScript编码时,经常会遇到需要复制对象或者数组的情况,但是有的时候它并不像我们预期的那样工作,下面就一起来看看吧!
各种类型的数据拷贝
让我们先从示例看起
拷贝数字
可以看到所有的结果都是正常的
拷贝字符串
可以看到结果都是正常的
拷贝数组(错误版)
我们可以看到以下示例
预期是
- 把list复制一份给box
- 把list下标为3的改为a,得到[1,2,3,'a']
- 把box下标为4的改为b,得到[1,2,3,4,'b']
实际效果是
- 改list时,box也同时被改了
- 改box时,list也被改了
拷贝对象(错误版)
从下例中我们可以看到
-
对象A只改了blog,应该得到
{ blog:'0', author:'Axjy' }
-
对象B只改了author,应该得到
{
blog:'jueJin',
author:'1'
}
但是我们可以看到最后结果,都被改成了
{
blog:'0',
author:'1'
}
可以看到【数组拷贝】和【对象拷贝】和我们预期的都不一样,【原数据】和【新数据】之间互相影响,从这里我们就可以引出【浅拷贝】的概念了。
浅拷贝
浅拷贝是对象的逐位复制。创建一个新对象,该对象具有原始对象中值的精确副本。如果对象的任何字段是对其他对象的引用,则只复制引用地址,即,复制内存地址。【默认情况下引用类型(object)都是浅拷贝】
简单理解就是:浅拷贝复制的是对象的引用地址,没有开辟新的栈,复制的结果是两个对象指向同一个地址,所以修改其中一个对象的属性,另一个对象的属性也跟着改变了。
深拷贝
深拷贝复制所有字段,并复制字段所指向的动态分配内存。深拷贝发生在对象及其引用的对象被复制时。【默认情况下基本数据类型(number,string,null,undefined,boolean)都是深拷贝。】
简单的理解就是:深拷贝会开辟新的栈,两个对象对应两个不同的地址,修改对象A的属性,并不会影响到对象B
数组深拷贝方案
数组实现深拷贝可以使用以下方法
- 使用
slice()
- 使用
concat()
- ES6扩展运算符
- Array.form()
let box = list.slice();//方案一
let box = [].concat(list); //方案二
let box = [...list];//方案三
let box = Array.from(list);//方案四
对象深拷贝之Object.assign问题
在说【对象深拷贝】方案之前,我们先来看看Object.assign()
看以下示例,单看结果,似乎是不错的
let B = Object.assign({},A);
但是如果我们做一些修改呢?
如以下示例
- 在对象A里面又嵌套了一层对象C,
- 【修改】A里面的C,
- 【结果】A里面的C被修改的同时,B也被修改了。
那么问题来了, Object.assign()是深拷贝还是浅拷贝?
定义
Object.assign()
方法用于将所有可枚举属性的值从一个或多个源对象分配到目标对象。它将返回目标对象。MDN文档
针对深拷贝
Object.assign()
拷贝的是(可枚举)属性值。假如源值是一个对象的引用,它仅仅会复制其引用值。
也就是说:如果对象的属性值为简单类型(如Number,String),通过Object.assign({},Obj)得到的新对象为深拷贝;如果属性值为对象或其它引用类型,那对于这个对象而言其实是浅拷贝的。
使用解构赋值和扩展运算符(...)时,你会发现情况和上面是一样的。
对象深拷贝方案
使用 JSON.stringify
用 JSON.stringify
把对象转换成字符串,再用JSON.parse
把字符串转换成新的对象,
注:可以转成 JSON 格式的对象才能使用这种方法,如果对象中包含 function 或 RegExp 则不能用这种方法。
let B = JSON.parse(JSON.stringify(A))
使用递归
这里有三点需要注意:
1、用new obj.constructor ()
构造函数新建一个空的对象,而不是使用{}
或者[]
,这样可以保持原形链的继承;
2、用obj.hasOwnProperty(key)
来判断属性是否来自原型链上,因为for..in..
也会遍历其原型链上的可枚举属性。
3、上面的函数用到递归算法,在函数有名字,而且名字以后也不会变的情况下,这样定义没有问题。但问题是这个函数的执行与函数名 factorial 紧紧耦合在了一起。为了消除这种紧密耦合的现象,需要使用 arguments.callee
。
var clone = function (obj) {
if(obj === null) return null
if(typeof obj !== 'object') return obj;
if(obj.constructor===Date) return new Date(obj);
if(obj.constructor === RegExp) return new RegExp(obj);
var newObj = new obj.constructor (); //保持继承链
for (var key in obj) {
if (obj.hasOwnProperty(key)) { //不遍历其原型链上的属性
var val = obj[key];
newObj[key] = typeof val === 'object' ? arguments.callee(val) : val; // 使用arguments.callee解除与函数名的耦合
}
}
return newObj;
};
此方法来源于:JS中如何进行对象的深拷贝;想要探索更多的深拷贝对象的方法可以看看这个深入 js 深拷贝对象
参考:
Shallow Vs Deep Copy In Javascript
Understanding Deep and Shallow Copy in Javascript
如果有收获的话就随手留个赞吧!😘