##前言 对象的拷贝是面试与实际开发中经常问到与应用到的知识。javascript提供的的数据操作api基本都是对象的浅拷贝。而深拷贝方法则比较少。由于经常使用到,于是把深拷贝与浅拷贝常见的方法记录下来。
1.什么是深拷贝和浅拷贝
浅拷贝和深拷贝都是对于JS
中的引用类型而言的,浅拷贝就只是复制对象的引用(堆和栈的关系,简单类型Undefined
,Null
,Boolean
,Number
和String
是存入堆,直接引用,object`` array
则是存入桟中,只用一个指针来引用值),如果拷贝后的对象发生变化,原对象也会发生变化。只有深拷贝才是真正地对对象的拷贝。
2.浅拷贝的实现
1.直接赋值
浅拷贝对象,如果对象的属性是个引用类型,那么值复制引用,而未复制真正的值。
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'}}
2.使用ES6+语法实现
const originArray = [1,2,3,4,5];
const originObj = {a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}};
const cloneArray1=Object.assign([], originArray) //[1,2,3,4,5]
const cloneObj1=Object.assign({}, originObj) //{a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}}
const cloneArray2=[...originArray] ////[1,2,3,4,5]
const cloneObj2={...originObj} //{a:'a',b:'b',c:[1,2,3],d:{dd:'dd'}}
3.深拷贝的实现
深拷贝常用的方法主要有两种:
- 利用
JSON
对象的parse
和stringify
; - 利用递归来实现每一层都重新创建对象并赋值
1.利用JSON来实现
JSON.stringify
是将一个 JavaScript
值转成一个 JSON
字符串。
JSON.parse
是将一个 JSON
字符串转成一个 JavaScript
值或对象。
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 对象之后无法反解析为 原对象类型,解析后的值仍然是 JSON 格式的字符串。
- 正则 RegExp 对象 RegExp 对象序列化后为一个普通的 javascript 对象,同样不符合预期。
- undefined 序列化之后直接被过滤掉,丢失拷贝的属性。
- NaN 序列化之后为 null,同样不符合预期结果。
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"}
因此,如果对象里面如果存在上面三种属性,就不可以使用这种方法进行深拷贝了。
2.利用递归实现
递归拷贝顾名思义就是对每一层数据都实现一次创建对象->对象赋值的操作。
---直接上代码---
function deepClone(source){
const target=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
}
函数验证
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'}};
再来试试带有函数的对象:
const originObj = {
name:'axuebin',
sayHello:function(){
console.log('Hello World');
}
}
console.log(originObj); // {name: "axuebin", sayHello: ƒ}
const cloneObj = deepClone(originObj);
console.log(cloneObj); // {name: "axuebin", sayHello: ƒ}