JS浅拷贝和深拷贝在平时工作中可能不是很在意,但是相信大家都知道 Vue.js 和 react.js 等框架,这里面的 state 、 props 处理的时候就涉及到这个拷贝,这些框架都是数据驱动视图,也就是说数据模型的变更相应的页面视图也会改变,试想一下,如果后台返回的数据是一个多级的对象,而你只是对它浅拷贝和其他处理后用在多处地方,当这个拷贝对象深层级做了变更后,原对象的深层级其实也一样变更了,这就不是我们想要的。比如下面这个例子:

1
2
3
4
5
6
let obj = {
a: 10,
b: [1, 2, 3],
c: { x: 100 },
d: /^[0-9]{8}$/
};

然后我们对其实行浅拷贝,实现浅拷贝有下面两种方式:

1
2
3
4
5
6
7
8
9
10
// ES6
let obj2 = {...obj};

// ES5
let obj2 = {};
for(key in obj){
if(!obj.hasOwnProperty(key)) break;
obj2[key] = obj[key];
}
console.log(obj2);

此时我们在控制台打印出来,并看不出什么问题,我们在控制台做进一步处理,对拷贝的对象的第三层的 x 属性做更改,

1
2
obj2.c.x = 123;
console.log(obj,obj2);

我们发现,obj 和 obj2 的 x 属性都发生了变更,究其原因,是因为浅拷贝只是拷贝它的第二级数据,对于复杂类型的拷贝就只是拷贝它的引用,也就是地址,在上面案例中也就是 obj 和 obj2 它们的 c 属性都指向同一个地址,而这个地址所存储的是一个对象,当对象被修改,自然地另一个引用也会变更,如何来解决这个问题呢?我们就需要使用到深克隆。

1
2
3
let obj2 = JSON.parse(JSON.stringify(obj));
console.log(obj2);
// {a: 10, b: [1, 2, 3], c: {x: 100}}, d: {}}

我们使用了 JSON 对象所提供的方法把原对象转换为字符串,然后再转成 json 对象,这样可以很简单的实现大部分常规对象的深拷贝,但是存在弊端,会发现对象中的正则在转成字符串的时候是一个空对象,不仅仅是正则,还有 null 、 Date 和 Function 也会改变,所以需要通过遍历来封装一个深拷贝函数,然后对这几种特殊的类型特殊处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function deepClone(obj){
if(obj === null) return null;
if(typeof(obj) !== 'object') return obj;
if(obj instanceof RegExp) return new RegExp(obj);
if(obj instanceof Date) return new Date(obj);
// 不直接创建空对象 这样是为了创建和参数对象类型一致的实例
let newObj = new obj.constructor;
for(key in obj){
if(obj.hasOwnProperty(key)){
// 递归调用
newObj[key] = deepClone(obj[key]);
}
}
return newObj;
}

然后,我们可以深层次判断克隆对象是否和原有对象相等了,结果显然,它们不是同一个东西。

1
2
3
4
let obj2 = deepClone(obj);
console.log(obj === obj2); // false
console.log(obj.b === obj2.b); // false
console.log(obj.d === obj2.d); // false