对象的深浅拷贝,一直是老生常谈的话题,平台上的文章数量可谓是汗牛充栋,要从这块素材里找突破几乎是不可能。索性我就写一篇文章,积累一下本身的学习心得,以便后续复习的时候,能有一个比较清晰的思路。javascript
浅拷贝:将数据中全部的数据引用下来,并指向同一个存放地址,拷贝的数据修改以后,会对原数据产生反作用。前端
深拷贝:将数据中全部的数据拷贝下来,对拷贝以后的数据进行修改不会对原始数据产生反作用。java
业务中,不少时候你作的是浅拷贝,若是不影响业务逻辑,你可能不关心这些东西。面试
引用类型的等号赋值是最多见浅拷贝,以下所示:数组
var obj = {
name: 'Nick'
}
var newObj = obj
复制代码
此时你修改 newObj.name = 'Chen'
,则会使得 obj
也会跟着变化,这是由于声明的 obj
属于引用类型的变量,存在了全局做用域下的堆内存中。赋值给 newObj
,只是将内存的地址赋值给了它,因此修改 newObj
的属性,也就是修改了堆内存中数据的属性,从而 obj
也会跟着改变。性能优化
var obj = {
name: 'Nick'
}
var newObj = obj
newObj.name = 'Chen'
console.log(obj.name) // 'Chen'
console.log(newObj.name) // 'Chen'
复制代码
你觉得 Object.assign
是深拷贝方法,其实否则。它也是浅拷贝,只不过是第一级的原始类型的数据,不受牵连,引用类型仍是会被篡改,咱们用数听说话:markdown
var obj = {
name: 'Nick',
hobby: ['code', 'movie', 'travel', { a: 1 }]
}
var newObj = Object.assign({}, obj)
newObj.name = 'Chen'
newObj.hobby[0] = 'codeing'
newObj.hobby[3].a = 2
console.log('obj', obj)
console.log('newObj', newObj)
复制代码
打印结果以下:函数
绿色箭头表明原始类型,没有被篡改。红色箭头表明的是引用类型,都随着 newObj
的修改而变化。工具
它比较特殊,若是要拷贝的对象,第一层是原始类型,则为深拷贝。若是是引用类型,则为浅拷贝,不妨作个小实验:post
var obj = {
name: 'Nick',
salary: {
high: 1,
mid: 2,
low: 3
}
}
var newObj = { ...obj }
newObj.name = 'Chen'
newObj.salary.high = 2
console.log(obj)
console.log(newObj)
复制代码
obj
的 name
属性没有被改变,salary
中的 high
被改为了 2。
因此咱们若是想用 ...
扩展运算符完成深拷贝,就得这样操做:
var obj = {
name: 'Nick',
salary: {
high: 1,
mid: 2,
low: 3
}
}
var newObj = {
...obj,
salary: {
...obj.salary
}
}
复制代码
我以为这样操做的人,确定是有毛病。
不少有志之士,会在代码中使用这种方式去作深拷贝。固然,多数业务场景中,这种方式仍是比较香的,可是仍是会有那么些状况,会出现大大小小的问题。
var obj = {
name: 'Nick',
hobby: ['code', 'movie', 'travel', { a: 1 }],
callback: function() {
console.log('test')
}
}
var newObj = JSON.parse(JSON.stringify(obj))
newObj.name = 'Chen'
newObj.hobby[0] = 'codeing'
newObj.hobby[3].a = 2
console.log('obj', obj)
console.log('newObj', newObj)
复制代码
确实没有被关联到,数据已经脱离了控制,可是函数 callback
么的了。
var obj = {
name: 'Nick',
date: [new Date(1621259998866), new Date(1621259998866)],
};
var newObj = JSON.parse(JSON.stringify(obj))
复制代码
obj
中的 date
内的时间对象被执行了。
var obj = {
name: 'Nick',
date: new RegExp('\\s+'),
};
var newObj = JSON.parse(JSON.stringify(obj));
obj.name = 'Chen'
复制代码
拷贝以后,date
变成了一个空值。
var obj = {
name: undefiend
}
var newObj = JSON.parse(JSON.stringify(obj));
复制代码
undefiend
在拷贝的过程当中,被丢失了。
var obj = {
name1: NaN,
name2: Infinity,
name3: -Infinity
}
var newObj = JSON.parse(JSON.stringify(obj))
复制代码
直接所有变成
null
,不跟你嘻嘻哈哈,可是这种状况应该也很少。
function Animal(name) {
this.name = name
}
var animal = new Animal('dog')
var obj = {
test: animal
}
var newObj = JSON.parse(JSON.stringify(obj))
复制代码
直接就把构造函数给丢了,拷贝以后,直接指向了 Object
。
诸如上述种种的状况,在真实开发环境中遇到的可能不是不少,可是你真的遇到了,在不知情的状况下,可能会耗费一些没必要要的时间去找出问题所在。
首先,大可使用 lodash.cloneDeep
这类工具实现深拷贝,有工具不用,哎,放着玩儿?
这里我要手动写一个深拷贝,从中能够学习到一些小知识点,爱看不看吧,我写给本身看。
var obj = {
name: 'Nick',
date: [new Date(1621261792177)],
callback: function() { console.log('shadiao') },
link: undefined
}
function deepClone(origin) {
if(origin === null) return null
if(typeof origin !== 'object') return origin;
if(origin.constructor === Date) return new Date(origin);
// 接受两个参数,origin 是原对象
var _target = origin.constructor() //保持继承链
// 循环 origin
for(var key in origin) {
//不遍历其原型链上的属性
if (origin.hasOwnProperty(key)) {
// 若是 origin[key] 是一个引用类型的值,则进入递归逻辑
if (typeof origin[key] === 'object' && origin[key] !== null) {
// 进入递归,此时原始值就是 origin[key],被赋值的对象是 _target[key]
// 注意,上述第一次声明的 _target 将会贯穿整个递归,后续全部的赋值,都将会被 return 到 _target
_target[key] = deepClone(origin[key])
} else {
// 若是不是对象或数组,则进入此逻辑,直接赋值给 _target[key]
_target[key] = origin[key]
}
}
}
// for...in 循环结束后,return 当前上下文的 _target 值
return _target
}
const newObj = deepClone(obj)
复制代码
上述 obj
对象的属性都被完整的拷贝下来了。
上述代码中,有一个关键步骤,若是理解了它,基本上你就理解为何能够实现递归赋值,咱们来看下面这段代码:
function test() {
var obj = {}
const _obj = test1(obj)
console.log('obj', obj)
console.log('_obj', _obj)
console.log(_obj === obj)
}
function test1(_obj) {
_obj.a = 1
return _obj
}
test()
复制代码
上述代码,在函数 test
内部声明 obj
对象,并将其以参数的形式,传递给 test1
方法。test1
内部的操做是给传进来的 _obj
参数赋值一个 a
属性,而且 return _obj
。
此时查看打印结果,obj
被也被添加了 a
属性,而且 _obj
全等于 obj
。这说明它们指向了同一个内存地址,就是 test
内的函数做用域。在《JavaScript 高级程序设计》第 86 页,对引用类型在函数之间的传递的知识有详细的分析。
利用这个原理,上述 deepClone
方法内部,执行递归的时候,所传进去的 _target[key]
,其实这个 _target
就是第一次执行 deepClone
的引用类型变量,后续递归操做对 _target[key]
的赋值,都将反映到最初的 _target
。最后函数执行结束,return _target
即是最终递归深拷贝后的最终值。
这个知识点很是细节,我不敢说会在业务开发中大量用到。但至少当你遇到这类问题的时候,你不会一头雾水、伤春悲秋,以为本身不适合这个行业。再一次强调,基础知识很重要,不要小看这些平时不起眼的知识,真到了拼刺刀的时候,你一无所知。
打通任督二脉的前端环境变量 — env 点赞数👍 228
Vite 2.0 + React + Ant Design 4.0 搭建开发环境 点赞数👍 385
面不面试的,你都得懂原型和原型链 点赞数👍 593
Vue 3 和 Webpack 5 来了,手动搭建的知识该更新了 点赞数👍 521
换一个角度分析,网页性能优化 点赞数👍 200
你好,谈谈你对前端路由的理解 点赞数👍 625
之前我没得选,如今我只想用 Array.prototype.reduce 点赞数👍 588
无处不在的发布订阅模式 —— 此次必定 点赞数👍 164
聊聊 JSX 和虚拟 DOM 点赞数👍 110