深拷贝
前言
写在前面,前几天面试的时候,被问到深拷贝有什么方式实现的时候。脑袋居然一空,只记得可以转成字符串和递归,但是具体的核心原理居然一点也想不起来了。所以为了让自己想起来,我决定重新查阅文章,并且自己编写一篇文章来加深记忆。
以下的描述为阅读了网上的文章之后结合自己的理解去描述的,如果有表达不清晰或者写得不正确的地方,还请见谅,以后我会随着自己的深入学习,继续回来修改这篇文章。
基本数据类型 和 引用数据类型
拷贝,其实就是字面意思,当我们为了复制一个变量的值到另一个变量的一种行为。不过这里会存在两种情况。
我们都知道数据类型分为,基本数据类型 和 引用数据类型。基本数据类型指的就是 String,Boolean,null,undefined,Number
。
基本数据类型一般是存储在栈内存中,复制的时候是值得直接传递。例如说:
var a = 123
var b = a
那么这个时候,会在栈内存中新分配一个空间,然后把value复制给新得变量。
如下图:
下面我们再来介绍一下引用数据类型
引用数据类型,其实就是数据会存放再堆内存中的。而在栈中的变量存放的是指向堆内存的地址。
而如果你想复制一个变量,给另一个变量,其实复制的只是引用地址。
var a = new Object()
var b = a
如下图是复制后的效果
image深拷贝和浅拷贝
知道了上面的这部分基础知识之后,下面就很好理解了。所谓的浅拷贝其实就是仅仅复制的是地址。而所谓的深拷贝,其实就是不仅仅复制的是引用地址,而是复制堆内存中的对象本身。
深拷贝完之后,修改原本的对象数据的时候,新复制出来的数据是不会发生变化的。
浅拷贝很简单,就不需要再说了,下面就直接上深拷贝的实现方式
通过递归的方法去实现
function deepClone(obj) {
var target = {}
for(var key in obj) {
// 过滤掉原型链上的可遍历的数据(即只复制自身的属性)
if(Object.peototype.hasOwnProperty.call(obj, key)){
if(typeof obj[key] === 'object'){
target[key] = deepClone(obj[key])
} else {
target[key] = obj[key]
}
}
}
return target
}
通过字符串的方式去实现
function(obj) {
// 其实就是把对象转成字符串(即把引用数据类型转成基本数据类型)
let strObj = JSON.stringify(obj)
let copyObj = JSON.parse(strObj)
return copyObj
}
通过Objcet.create() 实现
function deepCopy(obj) {
// 创建一个对象,并且给这个对象绑定一个原型
// getPrototypeOf 获取原型
var copy = Object.create(Object.getPrototypeOf(obj))
// 遍历获取自身的属性(包含不可遍历)----并且不会从原型链上遍历
var propNames = Objcet.getOwnPropertyNames(obj)
propNames.forEach(function(name) {
// 获取每一个属性的 描述 (即是否可遍历,能够执行delet,能否修改值)
var desc = Object.getOwnPropertyDescriptor(obj, name)
// 给一个目标对象添加一个属性,以及属性描述
Object.defineProperty(copy, name, desc)
})
return copy
}
补充一个递归的方法
function deepClone( obj, hash = new WeakMap() ){
// 如果是 null 或者 undefined 我就不进行拷贝操作了
if(obj === null ) return obj
if(obj instanceof Date ) return new Date( obj )
if(obj instanceof RegExp) return new RegExp(obj)
// obj 可能是对象或者普通值,如果只是个函数的话,也不进行拷贝
if(typeof obj !== "object") return obj
// 是对象的话就要进行拷贝了
if(hash.get(obj)) return hash.get(obj)
// 找到的是所属类原型上的 constructor,而原型上的 construction 指向的是当前类的本身
let cloneObj = new obj.constructor()
hash.set(obj, cloneObj);
for(let key in obj) {
if(obj.hasOwnProperty(key)) {
// 实现一个递归拷贝
cloneObj[key] = deepClone(obj[key], hash)
}
}
return cloneObj
}
// test
let obj = {
name:1,
address:{
x:100
}
}
obj.o = obj // 对象存在循环引用的情况
let d = deepClone(obj)
obj.address.x = 200
console.log(d)```
总结
文章到这里就结束了,其实深拷贝的难点就在于一开始我们提到的。由于栈内存中存储的变量的值存储的是引用地址,而你想复制,只能通过把堆内存中的值遍历出来,然后重新添加,并且重新分配出新的堆内存和新的引用地址。
以上
网友评论