最近参加百度前端训练营有节课讲到了JS对象的深拷贝,于是上网搜了一下相关文章,发现这是面试高频考题,于是乎写篇文章总结一下。
一. JS中为什么需要用到深拷贝?
要说到为什么需要深拷贝,就不得不提一嘴JS的对象引用传递了。
什么是对象引用传递?
var a = {name:'卡卡罗特'}
var b = a ;
a===b // true
b.name = '贝吉塔'
a.name //'贝吉塔'
在这里我们用=号把a的值赋给了b,但实际上a给b传的是a指向的地址罢了。
如果一个变量绑定到一个非基本数据类型(Array, Function, Object),那么它只记录了一个内存地址,该地址存放了具体的数据。注意之前提到指向基本数据类型的变量相当于包含了数据,而现在指向非基本数据类型的变量本身是不包含数据的
对于引用类型的变量,==和===只会判断引用的地址是否相同,而不会判断对象具体里属性以及值是否相同。因此,如果两个变量指向相同的对象,则返回true。 什么是拷贝?
拷贝就是把父对象的的值遍历全都拷贝给子对象
var a = {name:'卡卡罗特'}
var b = Object.assign({}, a)
a===b // false
b.name = '贝吉塔'
a.name //'卡卡罗特'
上面代码将原始对象拷贝到一个空对象,就得到了原始对象的克隆,这时候a与b指向的是不同的栈对象,所以对b.name重新复制也不会影响到a.name。但是如果a.name是一个对象的引用,而不是一个字符串,那么上面的代码也会遇到一些问题,参考如下代码:
var a = {name:{firstName:'卡卡罗特',lastName:'孙悟空'}}
var b = Object.assign({}, a)
a===b // false
b.name.firstName = '贝吉塔'
a.name.firstName //'贝吉塔'
这时候我们发现改变b.name.firstName会影响到a.name.firstName 因为Object.assign()方法只是浅层拷贝,a.name是一个栈对象的引用,赋值给b时,b.name也同样是这个栈对象的引用
在很多情况下我们只是想获得一个独立的、与父对象所有属性值相同的的对象,所以我们需要深拷贝。
二.如何进行深拷贝?
1. 使用JSON.parse()与JSON.stringify()对对象进行拷贝
var clone = function (obj) {
return JSON.parse(JSON.stringify(obj));
}
这种方法只适用于纯数据json对象,在其他一些情况下是有问题滴
var clone = function (obj) {
return JSON.parse(JSON.stringify(obj));
}
var a = {a:function(){console.log('hello world')},b:{c:1},c:[1,2,3],d:"卡卡罗特",e:new Date(),f:null,g:undefined}
var b = clone(a)
不难发现该方法会忽略值为function以及undefied的字段,而且对date类型的支持也不太友好。更要紧的是,上述方法只能克隆原始对象自身的值,不能克隆它继承的值,参考如下代码:
function Person (name) {
this.name = name
}
var Goku = new Person('卡卡罗特')
var newGoku = clone(Goku)
Goku.constructor === Person // true
newGoku.constructor === Object // true
2. 使用递归的方式实现深拷贝
function deepClone(source) {
if (!source) return
let target;
if (typeof source === 'object') {
// 根据source类型判断是新建一个数组还是对象
target = Array.isArray(source) ? [] : {}
// 遍历source,并且判断是source的属性才拷贝
for (let key in source) {
if (source.hasOwnProperty(key)) {
if (typeof source[key] !== 'object') {
target[key] = source[key]
} else {
// 如果内部属性存在复杂数据类型,使用递归实现深拷贝
target[key] = deepClone(source[key])
}
}
}
} else {
target = source
}
return target
}