在开始说深浅拷贝之前,需要提一下基础知识:
JavaScript 中的对象,只是对内存中某个地址的引用。这些引用是可以变的,即可以重新分配的。因此,仅复制一个引用,会导致 2 个对象指向同一个引用地址,当此引用地址的内容被修改时,所有指向此地址的都会被改变。
让我们看下一个代码示例:
var foo = { a: 'hello' }
console.log(foo.a) // 'hello'
var bar = foo
console.log(bar.a) // 'hello'
foo.a = 'world'
console.log(foo.a) // 'world'
console.log(bar.a) // 'world'
bar.a = 'hello world'
console.log(foo.a) // 'hello world'
console.log(bar.a) // 'hello world'
可以看到,foo
与 bar
指向同一个内存地址,当其中一个被修改时,另一个也会受影响。
浅拷贝 SHALLOW COPY
现在,我们来用一下浅拷贝, ...
展开运算符 和 Object.assign()
都可以实现浅拷贝。
var obj = { foo: 'foo', bar: 'bar' }
var copy = { ...obj }
console.log(obj) // {foo: "foo", bar: "bar"}
console.log(copy) // {foo: "foo", bar: "bar"}
obj.foo = 'abc'
console.log(obj) // {foo: "abc", bar: "bar"}
console.log(copy) // {foo: "foo", bar: "bar"}
OR
var obj = { foo: 'foo', bar: 'bar' }
var copy = Object.assign({}, obj)
console.log(obj) // {foo: "foo", bar: "bar"}
console.log(copy) // {foo: "foo", bar: "bar"}
obj.foo = 'abc'
console.log(obj) // {foo: "abc", bar: "bar"}
console.log(copy) // {foo: "foo", bar: "bar"}
通过上面两种方法拷贝的对象,修改时并没有相互影响,并且可以把多个源对象拷贝到目标对象。
var obj1 = { foo: 'foo' }
var obj2 = { bar: 'bar' }
var copySpread = { ...obj1, ...obj2 }
var copyAssign = Object.assign({}, obj1, obj2)
上面的方法,看起很爽,然而现实总是有各种办法让你低下高傲的头颅。 当对象的属性就是一个对象的时候,就完蛋了。让我们看下面的代码。
var foo = { a: 'abc', b: { c: '123' } }
var copy = { ...foo }
console.log(foo) // { a: 'abc', b: { c: '123' } }
console.log(copy) // { a: 'abc', b: { c: '123' } }
copy.a = 'edf'
copy.b.c = '456'
console.log(foo) // { a: 'abc', b: { c: '456' } }
console.log(copy) // { a: 'edf', b: { c: '456' } }
当 copy
修改 copy.b.c
的值时,foo
的值也改变了。。。
深拷贝 DEEP COPY
通过上面部分的代码,相信我们已经理解了浅拷贝,并知道了弊端。那么,就让我们祭出 深拷贝 吧!
第一种解决方案:通过JSON.stringify
将对象序列化为字符串,再通过 JSON.parse
反序列化它。
var foo = { a: 'abc', b: { c: '123' } }
var copy = JSON.parse(JSON.stringify(obj))
是的,没有错,你没有看错,就是这么简单。是不是准备重新抬起你高傲的头颅了? 然而现实还是一如既往的残酷,总是有各种办法让你低下高傲的头颅。
当对象的属性有循环引用时,有不可序列化的值类型时,例如是个 Date
对象,JSON.parse也只能将其解释为字符串,而不是Date对象。
那么,让我们把这些情况都考虑进去吧,写一个函数。
function deepCopy(obj) {
var copy;
if(obj == null || typeof obj != 'object') return obj
if(obj instanceof Date) {
copy = new Date()
copy = setTime(obj.getTime())
return copy
}
if(obj instanceof Array) {
copy = []
for(var i = 0, let = obj.length; i < len; i++) {
// 多维数组,递归拷贝
copy[i] = deepCopy(obj[i])
}
return copy
}
if(obj instanceof Function) {
copy = function() {
return obj.apply(this, arguments)
}
return copy
}
if(obj instanceof Object) {
copy = {}
for(var attr in obj) {
if(hasOwnProperty(attr)) {
copy[attr] = deepCopy(obj[attr])
}
}
return copy
}
throw new Error(`Unable to copy ${obj.constructor.name} as type isn't supported `)
}
总之,在 JavaScript
中复制对象,要考虑到对象的上下文和类型。
但是现实开发中,老板的要求是 用最少的时间,做最多的事,而不是用最多的时间,做一件精品
所以,Lodash
你值得拥有。
Node.js 的深拷贝
从8.0.0版开始,Node.js提供了与结构化克隆兼容的序列化api。
const v8 = require('v8');
const buf = v8.serialize({a: 'foo', b: new Date()});
const cloned = v8.deserialize(buf);
cloned.b.getMonth();