首先,我们应该明确深浅拷贝是针对Array
、Object
这样的复杂类型的。
一 常见的深浅拷贝的方法
(1)数组和对象的浅拷贝
如果是数组,我们可以利用数组的一些方法比如:slice
、concat
返回一个新数组的特性来实现拷贝。
//新旧数组都改变,对象指向同一块地址
var arr=['old',{"old":"old"},["old"],null,undefined];
var newArr=[];
//newArr=arr.concat();
newArr=arr.slice();
newArr[0]='new';
newArr[1].old="new";
newArr[2][0]="new";
console.log(arr);//[ 'old', { old: 'new' }, [ 'new' ], null, undefined ]
console.log(newArr);//[ 'new', { old: 'new' }, [ 'new' ], null, undefined ]
可以看到,当我们使用silce
或者concat
方法生成一个新数组时,如果我们改变值类型,那么改变互不影响;如果改变的是数组、对象这样的引用类型,那么无论新旧数组发生改变,两者都会变化(实际上我们拷贝的是地址或者说是对象或数组的引用)
Object.assign()
:ES6中定义的,将源对象(source)的所有可枚举属性复制到目标对象(target)。
Object.assign(target,...source);
var obj1={old1:"old1"};
var target=Object.assign(obj1);
target.old1="target1";
console.log(target.old1);//target1
console.log(obj1.old1);//target1
var obj=[{old:"old"}];
var target=Object.assign(obj);
target[0].old="new";
console.log(target);//[ { old: 'new' } ]
console.log(obj);//[ { old: 'new' } ]
(2)数组和对象的深拷贝
JSON.parse
:可以将JSON字符串反序列化成JS对象
JSON.stringify
:可以将JS对象序列化成JSON字符串
var arr=['old',{"old":"old"},["old"],null,undefined];
var newArr=[];
newArr=JSON.parse(JSON.stringify(arr));
newArr[0]='new';
newArr[1].old="new";
newArr[2][0]="new";
console.log(arr);//[ 'old', { old: 'old' }, [ 'old' ], null, undefined ]
console.log(newArr);//[ 'new', { old: 'new' }, [ 'new' ], null, null ]
我们发现,当使用JSON.parse
和JSON.stringify
进行拷贝时,拷贝前后的两个数组是互不相干的,对拷贝产生的数组和源数组的各自操作互不影响。
这种方法的缺点:
Number
、String
、Boolean
、Array
、扁平对象
等能够被json
表示的数据结构,因此函数这种不能被json
表示的类型,如fucntion
、RegExp
类型将不能被正确处理。var arr=[1,"old",[1,2,3],function(){return "old";},undefined,new RegExp("old")];
var newArr=[];
newArr=JSON.parse(JSON.stringify(arr));
console.log(arr);//[ 1, 'old', [ 1, 2, 3 ], [Function], undefined, /old/ ]
console.log(newArr);//[ 1, 'old', [ 1, 2, 3 ], null, null, {} ]
constructor
,也就是拷贝后,无论这个对象原来的构造函数是什么,在深拷贝之后都会变成object
。二 深浅拷贝的区别
JavaScript存储对象都是存地址的。
浅拷贝:会导致 obj1 和obj2 指向同一块内存地址。改变了其中一方的内容,都是在原来的内存上做修改会导致拷贝对象和源对象都发生改变,
深拷贝:开辟一块新的内存地址,将原对象的各个属性逐个复制进去。对拷贝对象和源对象各自的操作互不影响。
三 自己实现深浅拷贝
浅拷贝:
function shallowCopy(obj){
if(typeof obj !== "object") return;//只针对object
var newObj=obj instanceof Array?[]:{};//根据obj的类型新建一个数组或者对象
for(var i in obj){//遍历obj的所有可枚举属性,包括实例上的和原型上的
if(obj.hasOwnProperty(i)){//只拷贝实例上的属性
newObj[i]=obj[i];
}
}
return newObj;
}
var arr=[1,"old",[1,2,3],function(){return "old";},undefined,new RegExp("old")];
var newArr=shallowCopy(arr);
arr[2][2]=4;
newArr[4]="new";
console.log(arr);//[ 1, 'old', [ 1, 2, 4 ], [Function], undefined, /old/ ]
console.log(newArr);//[ 1, 'old', [ 1, 2, 4 ], [Function], 'new', /old/ ]
深拷贝:
function deepCopy(obj){
if(typeof obj !== "object") return;//只针对object
var newObj=obj instanceof Array?[]:{};//根据obj的类型新建一个数组或者对象
for(var i in obj){//遍历obj的所有可枚举属性,包括实例上的和原型上的
if(obj.hasOwnProperty(i)){//只拷贝实例上的属性
newObj[i] = (typeof obj[i]=="object") ? deepCopy(obj[i]):obj[i];//注意
}
}
return newObj;
}
var arr=[1,"old",[1,2,3],function(){return "old";},undefined,new RegExp("old")];
var newArr=deepCopy(arr);
arr[2][2]=4;
newArr[4]="new";
console.log(arr);//[ 1, 'old', [ 1, 2, 4 ], [Function], undefined, /old/ ]
console.log(newArr);//[ 1, 'old', [ 1, 2, 3 ], [Function], 'new', {} ]
注意,深拷贝的实现使用了递归,性能不如浅拷贝。在实际开发中我们应该根据实际情况进行选择。
四 第三方库中的深浅拷贝
(1) jQuery——$.extend():深浅复制
$.extend( [boolean], object1, object2 );
用于将一个或多个对象的内容合并到目标对象。
boolean:默认为false,执行浅拷贝;如果设为true
,执行深拷贝。
注意:不支持第一个参数传递 false
var object1 = {
apple: 0,
banana: { weight: 52, price: 100 },
cherry: 97
};
var object2 = {
banana: { price: 200 },
durian: 100
};
$.extend(object1, object2 );
console.log(JSON.stringify(object1));//{"apple":0,"banana":{"price":200},"cherry":97,"durian":100}
//$.extend(true,object1, object2 );
//console.log(JSON.stringify(object1));//{"apple":0,"banana":{"weight":52,"price":200},"cherry":97,"durian":100}
$.extend()源码
(2)underscore——_.clone():浅复制
var x = {
a: 1,
b: { z: 0 }
};
var y = _.clone(x);
console.log(y === x); // false
console.log(y.b === x.b) // true
x.b.z = 100;
console.log(y.b.z); // 100
_.clone()源码:
_.clone = function(obj) {
if (!_.isObject(obj)) return obj;
return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
};
(3)lodash:
_.clone(value):浅拷贝
var objects = [{ 'a': 1 }, { 'b': 2 }];
var shallow = _.clone(objects);
console.log(shallow[0] === objects[0]);
// => true
_.cloneDeep(value):深拷贝
var objects = [{ 'a': 1 }, { 'b': 2 }];
var shallow = _.cloneDeep(objects);
console.log(shallow[0] === objects[0]);
// => false
JavaScript中的深浅拷贝其实实现了JavaScript中非构造函数的继承。
关于“非构造函数”的继承的更多问题参考:Javascript面向对象编程(三):非构造函数的继承
参考:JavaScript专题之深浅拷贝 #32
深入剖析 JavaScript 的深复制