作为一个即将放弃web开发的android来讲,无意间看到的问题,觉得这个js 对象和数组拷贝问题挺有意思的,也是看到了网络上很多的教程,掺杂着自己的感受集合了一个自己的技术笔记,给出的例子也都比较好理解。
对于基本数据类型,并没有深浅拷贝的说法,只有赋值(浅拷贝),至于我们后来即将所说的深浅拷贝都是对于引用数据类型来讲解的。
浅拷贝:浅拷贝的意思就是只复制“引用关系“,而未复制真正的值。这里注意,是引用关系,所以只要引用关系没变,最后都会影响原来的对象或者数组。看下面的例子,
const originArray = [1,2,3,4,5];
const originObj = {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};
const cloneArray = originArray;
const cloneObj = originObj;
console.log(cloneArray); // [1,2,3,4,5]
console.log(originObj); // {a:'a',b:'b',c:Array[3],d:{dd:'dd'}}
cloneArray.push(6);
cloneObj.a = {aa:'aa'};
console.log(cloneArray); // [1,2,3,4,5,6]
console.log(originArray); // [1,2,3,4,5,6]
console.log(cloneObj); // {a:{aa:'aa'},b:'b',c:Array[3],d:{dd:'dd'}}
console.log(originArray); // {a:{aa:'aa'},b:'b',c:Array[3],d:{dd:'dd'}}
深拷贝:深拷贝就是对目标的完全拷贝,不像浅拷贝那样只是复制了一层引用关系,而是连值也都进行复制。进行了深拷贝,谁也不会影响谁。目前实现深拷贝的方法不多,主要是两种:利用 JSON 对象中的 parse 、stringify和递归函数实现。
看下面例子:一个使用JSON 对象中的 parse 和 stringify来实现的深拷贝
const originArray = [1,2,3,4,5];
const cloneArray = JSON.parse(JSON.stringify(originArray));
console.log(cloneArray === originArray); // false
const originObj = {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};
const cloneObj = JSON.parse(JSON.stringify(originObj));
console.log(cloneObj === originObj); // false
cloneObj.a = 'aa';
cloneObj.c = [1,1,1];
cloneObj.d.dd = 'doubled';
console.log(cloneObj); // {a:'aa',b:'b',c:[1,1,1],d:{dd:'doubled'}};
console.log(originObj); // {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};
使用JSON 对象中的 parse 和 stringify是无法完成方法的拷贝的,因为undefined
、function
、symbol
会在JSON 对象中的 parse 和 stringify转换过程中被忽略。。。
看下面这个例子:
const originObj = {
name:'axuebin',
sayHello:function(){
console.log('Hello World');
}
}
console.log(originObj); // {name: "axuebin", sayHello: ƒ}
const cloneObj = JSON.parse(JSON.stringify(originObj));
console.log(cloneObj); // {name: "axuebin"} 这里的sayHello方法不见了
所以我们会用递归函数来实现深拷贝
const originObj = {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};
const cloneObj = deepClone(originObj);
console.log(cloneObj === originObj); // false
cloneObj.a = 'aa';
cloneObj.c = [1,1,1];
cloneObj.d.dd = 'doubled';
console.log(cloneObj); // {a:'aa',b:'b',c:[1,1,1],d:{dd:'doubled'}};
console.log(originObj); // {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};
这里是递归函数:
function deepClone(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] = deepClone(source[keys]);
}else{ // 如果不是,就直接赋值
targetObj[keys] = source[keys];
}
}
}
return targetObj;
}
JavaScript中的拷贝方法:我们知道在 JavaScript 中,数组有两个方法 concat 和 slice 是可以对原数组的拷贝的,这两个方法都不会修改原数组,而是返回一个修改后的新数组。同时,ES6 中 引入了 Object.assgn
方法和 ... 展开运算符也能实现对对象的拷贝
concact:该方法可以连接两个或者更多的数组,但是它不会修改已存在的数组,而是返回一个新数组。
const originArray = [1,2,3,4,5];
const cloneArray = originArray.concat();
console.log(cloneArray === originArray); // false
cloneArray.push(6); // [1,2,3,4,5,6]
console.log(originArray); [1,2,3,4,5];
看起来是深拷贝,我们再看下面例子:
const originArray = [1,[1,2,3],{a:1}];
const cloneArray = originArray.concat();
console.log(cloneArray === originArray); // false
cloneArray[1].push(4);
cloneArray[2].a = 2;
console.log(originArray); // [1,[1,2,3,4],{a:2}]
所以总结一下:concact:只对第一层是深拷贝,对其他层是浅拷贝。Slice也是一样道理,这里不写例子了。
Object.assign方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。
var obj1 = {a: {b: 1}};
var obj2 = Object.assign({}, obj1);
obj1.a.b = 2;
obj2.a.b // 2
... 展开运算符:
const originArray = [1,2,3,4,5,[6,7,8]];
const originObj = {a:1,b:{bb:1}};
const cloneArray = [...originArray];
cloneArray[0] = 0;
cloneArray[5].push(9);
console.log(originArray); // [1,2,3,4,5,[6,7,8,9]]
const cloneObj = {...originObj};
cloneObj.a = 2;
cloneObj.b.bb = 2;
console.log(originObj); // {a:1,b:{bb:2}}
结论:... 实现的是对象第一层的深拷贝。后面的只是拷贝的引用值。
集体总结:赋值运算符 = 实现的是浅拷贝,只拷贝对象的引用值;JavaScript 中数组和对象自带的拷贝方法都是“首层深拷贝,其他潜拷贝”;JSON.stringify 实现的是深拷贝,但是对目标对象有要求可以使用递归函数;若想真正意义上的深拷贝,请递归。