在开发的过程中,我们往往需要复制一个数据,在复制基本数据类型的时候不会出现问题,如string
、number
、null
等。
但是我们复制一个引用类型的数据时,往往会出现问题,如array
、object
。
浅拷贝
看下面这段代码
1 2 3 |
var arr1 = [1,2,3]; var arr2 = arr1; arr2.push(4) |
我们打印一下看看arr1
arr2
的结果
1 2 |
arr1: [1, 2, 3, 4] arr2: [1, 2, 3, 4] |
我们发现,改变arr2
的同时也改变了arr1
,WTF?
接下来我们看看对象
1 2 3 4 5 |
var obj1 = { name:'wclimb' }; var obj2 = obj1 obj2.age = 24 |
我们打印一下看看arr1``arr2
的结果
1 2 |
obj1: {name: "wclimb", age: 24} obj2: {name: "wclimb", age: 24} |
和预想的一样,都被影响了。
因为引用类型的复制,两个引用类型的指针都指向同一个堆内存
网上偶尔会看到有人说slice
、concat
是深拷贝,其实这两个是浅复制,如下
1 2 3 |
var arr1 = [1,2,3]; var arr2 = arr1.slice(); arr2.push(4) |
结果:
1 2 |
arr1 [1, 2, 3] arr2 [1, 2, 3, 4] |
数组元素只是基本数据类型不会有影响,那么我们看下面的
1 2 3 |
var arr1 = [1,2,3,[4]]; var arr2 = arr1.slice(); arr2[3].push(5) |
结果
1 2 |
arr1: [1,2,3,[4,5]] <----- 被影响了 arr2: [1,2,3,[4,5]] |
即使使用了slice
, 两个数组也相互影响了,类似方法除了slice
、concat
还有Array.from
、...
深拷贝和浅拷贝最根本的区别在于是否是真正获取了一个对象的拷贝实体,而不是引用
深拷贝
Object.assign
Object.assign可以进行一层的深度拷贝,也就是跟slice类型的效果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
var obj1 = { name: 'wclimb', test1: null, test2: undefined, test3: function(){alert(1)}, test4: {} }; var obj2 = Object.assign({}, obj1) obj2.age = 24 结果: obj1: { name: 'wclimb', test1: null, test2: undefined, test3: function(){alert(1)}, test4: {} }; obj2: { name: 'wclimb', test1: null, test2: undefined, test3: function(){alert(1)}, test4: {}, age: 24 }; |
但是我们看下面的例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
var obj1 = { name: 'wclimb', test1: null, test2: undefined, test3: function(){alert(1)}, test4: {} }; var obj2 = Object.assign({}, obj1) obj2.age = 24 obj2.test4.val = '1'; 结果: obj1: { name: 'wclimb', test1: null, test2: undefined, test3: function(){alert(1)}, test4: {val: 1} <----- 被影响了 }; obj2: { name: 'wclimb', test1: null, test2: undefined, test3: function(){alert(1)}, test4: {val: 1}, age: 24 }; |
JSON.parse(JSON.stringify(obj))
说到深拷贝,你一定会想到JSON.parse(JSON.stringify(obj))
;
没错,这样可以完成一个深拷贝,看下面的例子:
1 2 3 4 5 |
var obj1 = { name: 'wclimb' }; var obj2 = JSON.parse(JSON.stringify(obj1)) obj2.age = 24 |
结果
1 2 |
obj1: {name: "wclimb"} <----- 没有被影响了 obj2: {name: "wclimb", age: 24} |
perfect,但是这个方法会有一个问题,如下例:
1 2 3 4 5 6 7 8 9 |
var var obj1 = { name: 'wclimb', test1: null, test2: undefined, test3: function(){alert(1)}, test4: {} }; var obj2 = JSON.parse(JSON.stringify(obj1)) obj2.age = 24 |
结果
1 2 3 4 5 6 7 8 9 10 11 12 13 |
obj1: { name: 'wclimb', test1: null, test2: undefined, test3: function(){alert(1)}, test4: {} }; obj2: { name: "wclimb", age: 24, test1: null, test4: {} } |
那两个个跑哪去了?所以这个方法不能够拷贝值为undefined
、function
深拷贝实现
那么怎么进行深拷贝呢?我的方法是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
function deepCopy(obj){ var result; var toString = Object.prototype.toString if(toString.call(obj) === '[object Array]'){ result = [] for(var i = 0 ;i < obj.length; i++){ result[i] = deepCopy(obj[i]) } }else if(toString.call(obj) === '[object Object]'){ result = {}; for (var key in obj) { if (obj.hasOwnProperty(key)) { result[key] = deepCopy(obj[key]) } } }else{ return obj } return result } 测试一下: var obj1 = { name: 'wclimb', test1: null, test2: undefined, test3: function(){alert(1)}, test4: {} }; var obj2 = deepCopy(obj1) obj2.age = 24 obj2.test4.val = '1'; 返回: obj1: { name: 'wclimb', test1: null, test2: undefined, test3: function(){alert(1)}, test4: {} <----- 没有被影响了 }; obj2: { age: 24, name: 'wclimb', test1: null, test2: undefined, test3: function(){alert(1)}, test4: {val: '1'} }; |
jQuery的实现
1 2 3 4 5 6 7 8 9 |
var obj1 = { name: 'wclimb', test1: null, test2: undefined, test3: function(){alert(1)}, test4: {} }; var obj2 = $.extend(true, {}, obj1) obj2.age = 24 |
lodash的实现
1 2 3 4 5 6 7 8 9 |
var obj1 = { name: 'wclimb', test1: null, test2: undefined, test3: function(){alert(1)}, test4: {} }; var obj2 = _.cloneDeep(obj1) obj2.age = 24 |
转自 http://www.wclimb.site/2018/07/29/JavsScript%E4%B9%8B%E6%B5%85%E6%8B%B7%E8%B4%9D%E4%B8%8E%E6%B7%B1%E6%8B%B7%E8%B4%9D/