前言:在说拷贝之前,先说说js中的数据存储,我们知道js这广义上数据类型分为两种,原始数据类型和引用数据类型,原始数据类型包括string,boolean,number,null,undefined,symbol,引用类型有Function,Array,Object,Date,RegExp等等其实这些都可以归总为Object类型。
对于原始类型数据,赋值即是拷贝,因为原始类型栈里存的就是其值。对于复杂类型来说,变量存储的是对象在堆空间里的地址值,如果进行赋值,赋值仅仅是将其地址值给另一个变量,实际上这两个变量都指向同一地址。
//原始类型赋值,a,b的值相互不影响
let a = 23
let b = a
//复杂类型,当d的name改变,c的name改变,因为它们指向的就是同一对象
let c = {
name:"sd"
}
let d = c
现在,我们知道了拷贝就是为了生成两份互相不影响的数据,同时我们知道原始类型的赋值即拷贝,而对象的属性可能由原始数据类型组成,也有可能由对象类型组成,所以我们分类考虑。
var obj = {
name:"张三",
time: 12312313
}
//1、赋值操作
var obj2 = obj
//2、拷贝
var obj3 = {}
for(let key in obj) {
obj3[key] = obj[key]
}
如上,第二个我们简单实现了拷贝。不过稍微思考一下,我们会发现,如果obj的属性也是一个复杂类型呢?
var obj = {
o : {
name:"asd"
},
age:23
}
var obj2 = {}
for(let key in obj) {
obj2[key] = obj[key]
}
obj2.o.name = "张三"
obj.o.name //张三
如上代码,按照开始的方法我们进行拷贝,拷贝后,我们发现obj.o与obj2.o指向的还是同一对象。如果我们需要完全拷贝,就得对obj.o也进行遍历。
得出结论
1、浅拷贝:浅拷贝是会将对象的每个属性进行依次复制,但是当对象的属性值是引用类型时,实质复制的是其引用,当引用指向的值改变时也会跟着变化。(ES6的Object.assign方法就是进行浅拷贝)
2、深拷贝:深拷贝复制变量值,对于非基本类型的变量,则递归至基本类型变量后,再复制。 深拷贝后的对象与原来的对象是完全隔离的,互不影响,对一个对象的修改并不会影响另一个对象。
3、实现深拷贝
function deepCopy(o) {
if(o instanceof Date) return new Date(o) //日期类直接返回
if(o instanceof RegExp) return new RegExp(o) //正则直接返回
if(o === null || typeof o !== "object") return o //原始类型直接返回
let c = new o.constructor()
for(let key in o) {
if(o.hasOwnProperty(key)) {
c[key] = deepCopy(o[key])
}
}
return c
}
优化,上面的深拷贝还有一点问题,如果出现循环引用,将会陷入死递归,导致程序崩溃。
function deepCopy(o,map = new WeakMap()) {
if(o instanceof Date) return new Date(o) //日期类直接返回
if(o instanceof RegExp) return new RegExp(o) //正则直接返回
if(o === null || typeof o !== "object") return o //原始类型直接返回
if(map.has(o)) {
return map.get(o)
}
let c = new o.constructor()
map.set(o,c)
for(let key in o) {
if(o.hasOwnProperty(key)) {
c[key] = deepCopy(o[key])
}
}
return c
}