Js 浅拷贝与深拷贝
Js 浅拷贝与深拷贝只针对像 Object 或 Array 这类引用数据类型,想了解Js的一些数据类型的同学,可详见我的另一篇文章 js 数据类型及类型判断,这里就不多详细说明。
浅拷贝:
像我们在对一些数据类型进行简单的赋值处理,然后用新的变量改变里面的属性的值后,会发现原本的数据也被改变了,如下示例:
let obj = {
a: '12',
b: 2
}
let simpleCopy = obj
simpleCopy.b = 5 // 或者执行 obj.b = 5 ,输出结果一致
console.log(obj) // {a: '12', b: 5}
console.log(simpleCopy) // {a: '12', b: 5}
这是因为引用数据类型的值是按引用访问的。obj.b和simpleCopy.b都是指向存的值的地址,而不是值本身。
深拷贝:
在我们处理数据时,有些时候肯定不想自己原来的数据被替换或者更改,深拷贝就是解决这方面的问题。下面我会列举一些常见的深拷贝的方式。
方式一:Object.assign
Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象source复制到目标对象。它将返回目标对象target。但是我们需要注意的是,它只对最外层的进行深拷贝,也就是当对象内嵌套有对象的时候,被嵌套的对象进行的还是浅拷贝。
let obj = {
a: '12',
b: 2,
c: {
K1: NaN, K2: function(){
} }
}
let deepCopy = Object.assign({
}, obj)
deepCopy.b = 5
deepCopy.c.K1 = Infinity
console.log(obj)
console.log(deepCopy)
输出结果:
方式二:JSON.parse 和 JSON.stringify
这是一个常见的深拷贝实现的方式,此前我也常用这个方法进行一些数据处理。
let obj = {
a: '12',
b: 2,
c: {
K1: NaN, K2: function(){
} }
}
let deepCopy = JSON.parse(JSON.stringify(obj))
deepCopy.b = 5
deepCopy.c.K1 = Infinity
console.log(obj)
console.log(deepCopy)
输出结果:
缺点: 这里我们看出来好像没啥问题,也成功拷贝一份数据,并且不影响原来的数据。但是JSON.stringify这个方式在数据转换的时候,一些由构造函数生成的数据会丢失对象的constructor。像下面情况:
let obj = {
a: '12',
b: 2,
c: new Date(),
d: NaN
}
let deepCopy = JSON.parse(JSON.stringify(obj))
deepCopy.b = 5
console.log(obj)
console.log(deepCopy)
输出结果:
像Date、Error、RegExp、function、NaN、Infinity、-Infinity 和 undefined等等数据使用 JSON.stringify 方法都会使原本的值丢失或是被替换。这样的话,在一些情境下使用这类方式就不太行。
方式三:deepCopy 递归赋值(推荐)
比较上面的几个深度拷贝的方式,都或多或少有些缺点,所以我们可以自己封装一个深度拷贝的方法。
function cloneDepth(initalObj, finalObj = {
}) {
let bol = isArrayOrObject(initalObj)
if (!bol) return initalObj
Object.keys(initalObj).forEach((key) => {
bol = isArrayOrObject(initalObj[key])
finalObj[key] = bol ? cloneDepth(initalObj[key], bol === 'Array' ? [] : {
}) : initalObj[key]
})
// // 或者采用 for in
// for (var key in initalObj) {
// bol = isArrayOrObject(initalObj[key])
// finalObj[key] = bol ? cloneDepth(initalObj[key], bol === 'Array' ? [] : {}) : initalObj[key]
// }
return finalObj
}
function isArrayOrObject(list) {
let isBool = false
switch (outputDataType(list)) {
case 'Object': isBool = 'Object'; break;
case 'Array': isBool = 'Array'; break;
default: isBool = false; break;
}
return isBool
}
function outputDataType(data) {
let dataTypeStr = Object.prototype.toString.call(data)
dataTypeStr = dataTypeStr.match(/\[object (\S*)\]/)[1]
return dataTypeStr
}
调用我们封装的方法
let obj = {
a: '12',
b: 2,
c: {
k1: '1', k2: 23, k3: true },
d: ['11', 2, null, undefined, new Date()],
e: function () {
},
f: null,
h: new Date(),
i: Infinity,
j: new RegExp('12'),
n: NaN
}
let deepCopy = cloneDepth(obj)
console.log(obj)
console.log(deepCopy)
输出结果:
跟原本的数据一模一样,这样封装的cloneDepth方法就可以好好使用了。