JS 浅拷贝与深拷贝
一、什么是浅拷贝
浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。
- 如果属性是基本类型,拷贝的就是基本类型的值;
- 如果属性是引用类型,拷贝的就是内存地址。
从上图可知,浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。如果其中一个对象改变了这个地址,会影响到另一个对象。
var a1 = {
b: { c: {} }
}
var a2 = shallowClone(a1) // 浅拷贝
a2.b.c === a1.b.c // true 新旧对象还是共享同一块内存
二、浅拷贝的实现方式
1、Object.assign()
Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象。
示例:
let city1 = {
name: 'Guangzhou',
mayor: { name: "Wen", age: 55 },
}
let city2 = Object.assign({}, city1)
console.log(city1) // { name: "Guangzhou", mayor: {name: 'Wen', age: 55} }
console.log(city2) // { name: "Guangzhou", mayor: {name: 'Wen', age: 55} }
let city3 = {
name: 'Guangzhou',
mayor: { name: "Wen", age: 55 },
}
let city4 = Object.assign({}, city3)
city4.mayor.name = "Xu"
city4.name = 'Shenzhen'
console.log(city3) // { name: "Guangzhou", mayor: {name: 'Xu', age: 55} }
console.log(city4) // { name: "Shenzhen", mayor: {name: 'Xu', age: 55} }
2、lodash 的 _.clone
lodash 的 clone 可以用来进行浅拷贝。
示例:
let city1 = {
name: 'Guangzhou',
mayor: {
name: "Wen",
age: 55
},
}
let city2 = _.clone(city1)
console.log(city1) // { name: "Guangzhou", mayor: {name: 'Wen', age: 55} }
console.log(city2) // { name: "Guangzhou", mayor: {name: 'Wen', age: 55} }
let city3 = {
name: 'Guangzhou',
mayor: {
name: "Wen",
age: 55
},
}
let city4 = _.clone(city3)
city4.mayor.name = "Xu"
city4.name = 'Shenzhen'
console.log(city3) // { name: "Guangzhou", mayor: {name: 'Xu', age: 55} }
console.log(city4) // { name: "Shenzhen", mayor: {name: 'Xu', age: 55} }
3、展开运算符
展开运算符是 ES6 新特性,可以使用 ...
来进行浅拷贝,与 Object.assign()
的功能相同。
示例:
let city1 = {
name: 'Guangzhou',
mayor: {
name: "Wen",
age: 55
},
}
let city2 = { ...city1}
console.log(city1) // { name: "Guangzhou", mayor: {name: 'Wen', age: 55} }
console.log(city2) // { name: "Guangzhou", mayor: {name: 'Wen', age: 55} }
let city3 = {
name: 'Guangzhou',
mayor: {
name: "Wen",
age: 55
},
}
let city4 = { ...city3}
city4.mayor.name = "Xu"
city4.name = 'Shenzhen'
console.log(city3) // { name: "Guangzhou", mayor: {name: 'Xu', age: 55} }
console.log(city4) // { name: "Shenzhen", mayor: {name: 'Xu', age: 55} }
4、Array.prototype.concat()
针对数组的浅拷贝可用 concat
实现。
示例:
let city1 = [1, 2, { name: 'Guangzhou' }];
let city2 = city1.concat();
console.log(city1); // [ 1, 2, { name: 'Guangzhou' } ]
console.log(city2); // [ 1, 2, { name: 'Guangzhou' } ]
let city3 = [1, 2, { name: 'Guangzhou' }];
let city4 = city3.concat();
city4[2].name = 'Shenzhen';
console.log(city3); // [ 1, 2, { name: 'Shenzhen' } ]
console.log(city4); // [ 1, 2, { name: 'Shenzhen' } ]
5、Array.prototype.slice()
针对数组的浅拷贝也可用 slice
实现。
示例:
let city1 = [1, 2, { name: 'Guangzhou' }];
let city2 = city1.slice();
console.log(city1); // [ 1, 2, { name: 'Guangzhou' } ]
console.log(city2); // [ 1, 2, { name: 'Guangzhou' } ]
let city3 = [1, 2, { name: 'Guangzhou' }];
let city4 = city3.slice();
city4[2].name = 'Shenzhen';
console.log(city3); // [ 1, 2, { name: 'Shenzhen' } ]
console.log(city4); // [ 1, 2, { name: 'Shenzhen' } ]
三、什么是深拷贝
深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象。
从上图可知,深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会影响原对象。
var a1 = {
b: { c: {} }
}
var a3 = deepClone(a3); // 深拷贝方法
a3.b.c === a1.b.c // false 新对象跟原对象不共享内存
四、深拷贝的实现方式
1、JSON.parse(JSON.stringify())
利用 JSON.stringify 将对象转成 JSON 字符串,再用JSON.parse把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。
示例:
let city1 = [1, 2, { name: 'Guangzhou' }];
let city2 = JSON.parse(JSON.stringify(city1))
console.log(city1); // [ 1, 2, { name: 'Guangzhou' } ]
console.log(city2); // [ 1, 2, { name: 'Guangzhou' } ]
let city3 = [1, 2, { name: 'Guangzhou' }];
let city4 = JSON.parse(JSON.stringify(city3))
city4[2].name = 'Shenzhen';
console.log(city3); // [ 1, 2, { name: 'Guangzhou' } ]
console.log(city4); // [ 1, 2, { name: 'Shenzhen' } ]
这种方法虽然可以实现数组或对象深拷贝,但不能处理函数和正则,这两者基于 JSON.stringify
和 JSON.parse
处理后,会分别变为空对象和 null
。
比如下面的例子:
let city1 = [1, 2, { name: 'Guangzhou' }, function () { }];
let city2 = JSON.parse(JSON.stringify(city1));
console.log(city1); // [ 1, 2, { name: 'Guangzhou' }, ƒ () ]
console.log(city2); // [ 1, 2, { name: 'Shenzhen' }, null ]
2、lodash 的 _.cloneDeep
lodash 的 cloneDeep 可以用来进行深拷贝。
示例:
let city1 = {
name: 'Guangzhou',
mayor: {
name: "Wen",
age: 55
},
}
let city2 = _.cloneDeep(city1)
console.log(city1) // { name: "Guangzhou", mayor: {name: 'Wen', age: 55} }
console.log(city2) // { name: "Guangzhou", mayor: {name: 'Wen', age: 55} }
let city3 = {
name: 'Guangzhou',
mayor: {
name: "Wen",
age: 55
},
}
let city4 = _.cloneDeep(city3)
city4.mayor.name = "Xu"
city4.name = 'Shenzhen'
console.log(city3) // { name: "Guangzhou", mayor: {name: 'Wen', age: 55} }
console.log(city4) // { name: "Shenzhen", mayor: {name: 'Xu', age: 55} }
3、jQuery 的 $.extend
$.extend(deepCopy, target, object1, [objectN])
jquery 提供一個 $.extend
可以用来实现深拷贝,第一个参数为 true,即为深拷贝模式。
示例:
let city1 = {
name: 'Guangzhou',
mayor: {
name: "Wen",
age: 55
},
}
let city2 = $.extend(true, {}, city1)
console.log(city1) // { name: "Guangzhou", mayor: {name: 'Wen', age: 55} }
console.log(city2) // { name: "Guangzhou", mayor: {name: 'Wen', age: 55} }
let city3 = {
name: 'Guangzhou',
mayor: {
name: "Wen",
age: 55
},
}
let city4 = $.extend(true, {}, city3)
city4.mayor.name = "Xu"
city4.name = 'Shenzhen'
console.log(city3) // { name: "Guangzhou", mayor: {name: 'Wen', age: 55} }
console.log(city4) // { name: "Shenzhen", mayor: {name: 'Xu', age: 55} }
4、手写深拷贝方法
递归方法实现深度克隆原理:进行遍历操作,直到里边都是基本数据类型,然后进行复制。
function deepCopy(obj) {
if (typeof obj !== 'object' || obj == null) {
// obj 是 null ,或者不是对象和数组,直接返回
return obj
}
// 初始化返回结果
let result
if (obj instanceof Array) {
result = []
} else {
result = {}
}
for (let key in obj) {
// 保证 key 不是原型的属性
if (obj.hasOwnProperty(key)) {
// 递归调用
result[key] = deepCopy(obj[key])
}
}
// 返回结果
return result
}
五、赋值、浅拷贝、深拷贝的区别
赋值示例:
let city1 = {
name: 'Guangzhou',
mayor: { name: "Wen", age: 55 },
}
let city2 = city1
console.log(city1) // { name: "Guangzhou", mayor: {name: 'Wen', age: 55} }
console.log(city2) // { name: "Guangzhou", mayor: {name: 'Wen', age: 55} }
let city3 = {
name: 'Guangzhou',
mayor: { name: "Wen", age: 55 },
}
let city4 = city3
city4.mayor.name = "Xu"
city4.name = 'Shenzhen'
console.log(city3) // { name: "Shenzhen", mayor: {name: 'Xu', age: 55} }
console.log(city4) // { name: "Shenzhen", mayor: {name: 'Xu', age: 55} }
let city5 = 'Guangzhou'
let city6 = city5
console.log(city6) // Guangzhou
city6 = 'Shenzhen'
console.log(city5) // Guangzhou
console.log(city6) // Shenzhen
总结:
说明 | |
---|---|
赋值(=) | |
浅拷贝 | |
深拷贝 |