We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
js 的浅拷贝与深拷贝在业务中时常有用到,关于浅拷贝与深拷贝的剖析文章层出不穷,本文是笔者对于深拷贝与浅拷贝的理解,一起来夯实 js 语言基础知识的理解吧。
浅拷贝
深拷贝
正文开始...
在阅读文章之前,本文主要从以下几个方面去探讨
为什么会有浅拷贝与深拷贝
浅拷贝是什么,深拷贝又是什么
浅拷贝与深拷贝有何区别
写一个例子佐证以上所有的观点
我们知道在js中基础数据类型是存放在栈内存中的,而引用数据类型是存放在栈地址引用的一个堆内存中。为什么两种数据会存放方式不同?这是一个值的思考的问题?我的猜想,引用数据类型是复杂的数据结构,本质上也是存放栈地址的引用,只是这个地址指向了另外一个堆内存空间,如果他们都是放在一起的话,就不太好区分你是基础数据类型,还是引用数据类型了。
js
首先它们都是拷贝,一个是浅,一个是深,我们先说结论,浅拷贝是基础数据类型的拷贝,只会拷贝一层,如果遇到拷贝的数据是引用数据,那么浅拷贝的数据与原有数据是同一份引用。
而深拷贝是遇到引用数据类型会创建一个新的对象,遍历原有对象,对新对象进行动态赋值,修改新对象引用不影响原有对象的属性值
我们用一个图来解释上面两段比较长的话
基础数据类型直接存放在栈地址内存中,而引用数据类型是存放在栈内存地址的引用中,这个引用实际上指向的区域是一块堆内存空间
在了解浅拷贝与深拷贝之前,我们先来了解下值拷贝
值拷贝
当我对原有基础数据类型与引用数据类型进行赋值时
赋值
用下面代码示例上图
var userName = 'Maic'; var age = 18; var userInfo = { name: 'Maic', age: 18, fav: { play1: 'ping pang', play2: 'basket ball' } }; var cacheUserName = userName; var cacheAge = age; // 对象值拷贝 var cacheUserInfo = userInfo; cacheUserName = 'Tom'; cacheAge = 20; cacheUserInfo.name = 'jake'; cacheUserInfo.age = 10; cacheUserInfo.fav.play1 = 'swim'; console.log(userName, age, userInfo, '------', cacheUserName, cacheAge, cacheUserInfo);
然后运行node index.js 从执行结果上来看
node index.js
Maic 18 { name: 'jake', age: 10, fav: { play1: 'swim', play2: 'basket ball' } } ------ Tom 20 { name: 'jake', age: 10, fav: { play1: 'swim', play2: 'basket ball' } }
因此可以得出结论
基础数据类型的赋值,是值的拷贝,会重新开辟一个栈空间,新拷贝的值修改不会影响原有数据类型
引用数据类型的赋值,原有引用数据与新赋值的数据指向的是同一份地址,修改引用数据的属性会影响原来的
以上是两种数据类型值的拷贝,貌似与浅拷贝还有离得有点远
于是我们看下对象扩展的浅拷贝
... // 对象浅拷贝 var cacheUserInfo = { ...userInfo } // 与下面等价 // var cacheUserInfo = Object.assign({}, userInfo); // 修改值拷贝后值 cacheUserName = 'Tom'; cacheAge = 20; cacheUserInfo.name = 'jake'; cacheUserInfo.age = 10; cacheUserInfo.fav.play1 = 'swim'; console.log(userName, age, userInfo, '------', cacheUserName, cacheAge, cacheUserInfo);
我使用了es6对象扩展对原有对象进行拷贝,那么此时结果是怎么样
es6
Maic 18 { name: 'Maic', age: 18, fav: { play1: 'swim', play2: 'basket ball' } } ------ Tom 20 { name: 'jake', age: 10, fav: { play1: 'swim', play2: 'basket ball' } }
不知道注意到没有,在引用数据类型的第一级如果这个属性是基础数据类型,那么修改并不会影响原有的值,如果属性是引用数据类型,那么这层结构会是一个值拷贝,修改新赋值属性,会影响到原有的对象属性
我们看下图理解下
因此我们可以得出结论,浅拷贝如果遇到基础数据类型,那么修改新值,不会影响原有的值,但是如果数据是引用数据类型,那么新修改的值会影响原有的值,因为新修改的与原修改的是同一份引用。
所以浅拷贝只会拷贝一层,如果数据是引用数据类型,实际上会直接引用同一份数据。
浅
深拷贝顾名思义,从表现层来看就是,就是为了修改新数据而不影响原有数据而产生的。
我们举个栗子
var userInfo = { name: 'Maic', age: 18, fav: { play1: 'ping pang', play2: 'basket ball' } };
当我需要修改userInfo.fav.play1的值,而不想影响原有userInfo对象的值,那么此时你就会想到深拷贝,那怎么深拷贝呢。
userInfo.fav.play1
userInfo
利用JSON.stringify(data)拷贝对象
JSON.stringify(data)
... const newUseInfo = JSON.parse(JSON.stringify(userInfo)); newUseInfo.fav.play1 = 'hello'; console.log(userInfo, '----', newUseInfo);
结果:
{ name: 'Maic', age: 18, fav: { play1: 'ping pang', play2: 'basket ball' } } ---------- { name: 'Maic', age: 18, fav: { play1: 'hello', play2: 'basket ball' } }
但是我们得考虑到JSON.stringify这种有种缺陷,必须是json对象,有其他比如方法这种会被自动过滤处理。而且如果json对象格式错误,就会抛出异常,所以我们看下另外一种方案。
JSON.stringify
json
方法
使用代理对象思想,将原有对象拷贝一份,然后再赋值
var userInfo = { name: 'Maic', age: 18, fav: { play1: 'ping pang', play2: 'basket ball' }, fav2: [ { a: 1, b: 2 }, { a: 3, b: 4 } ] }; const isType = (val) => { return (type) => Object.prototype.toString.call(val) === `[object ${type}]`; }; function deepMerge(target = {}) { const ret = {}; for (let key in target) { if (target.hasOwnProperty(key)) { if (isType(target[key])('Object')) { ret[key] = deepMerge(target[key]); } else { ret[key] = target[key]; } } } return ret; } const cacheObj = deepMerge(userInfo); cacheObj.fav.play1 = '111'; cacheObj.fav2[0].a = '666'; console.log(userInfo, '-----', cacheObj);
最终结果是
{ name: 'Maic', age: 18, fav: { play1: 'ping pang', play2: 'basket ball' }, fav2: [ { a: '666', b: 2 }, { a: 3, b: 4 } ] } ------- { name: 'Maic', age: 18, fav: { play1: '111', play2: 'basket ball' }, fav2: [ { a: '666', b: 2 }, { a: 3, b: 4 } ] }
但是如果数据中有数组,貌似数组的这种情况还是同一份值,那是因为直接赋值了
... function deepMerge(target = {}) { const ret = {}; for (let key in target) { if (target.hasOwnProperty(key)) { if (isType(target[key])('Object')) { ret[key] = deepMerge(target[key]) } else { // 是因为这里直接赋值了操作 ret[key] = target[key]; } } } return ret; }
于是需要多加一个条件,需要对数组进行判断
const isType = (val) => { return (type) => Object.prototype.toString.call(val) === `[object ${type}]`; }; function deepMerge(target) { const ret = Array.isArray(target) ? [] : {}; for (let key in target) { if (target.hasOwnProperty(key)) { if (isType(target[key])('Object')) { ret[key] = deepMerge(target[key]); } else if (isType(target[key])('Array')) { // 判断数组,并再次递归,用数组concat方法添加该数据 ret[key] = [].concat([...deepMerge(target[key])]); } else { ret[key] = target[key]; } } } return ret; } const cacheObj = deepMerge(userInfo); cacheObj.fav.play1 = '111'; cacheObj.fav2[0].a = '666'; console.log(userInfo, '----', cacheObj);
此时结果
{ name: 'Maic', age: 18, fav: { play1: 'ping pang', play2: 'basket ball' }, fav2: [ { a: 1, b: 2 }, { a: 3, b: 4 } ], fav3: [ 1, 2, 3 ] } ------- { name: 'Maic', age: 18, fav: { play1: '111', play2: 'basket ball' }, fav2: [ { a: '666', b: 2 }, { a: 3, b: 4 } ], fav3: [ 1, 2, 3 ] }
以上用一个图来进一步理解下
真是人才,深拷贝原来是这样的
通过以上例子,我们已经知道
浅拷贝如果拷贝对象内部的数据是基础数据类型,那么直接拷贝,新对象修改值,不会影响原有的值,如果拷贝的对象是一个引用数据类型,那么会是一个值的引用,此时新拷贝对象修改其值会影响原有的值。浅拷贝只会拷贝一层,拷贝的内部引用数据类型是同一份。
深拷贝本质上就是无论原对象值是基础数据类型,还是引用数据类型,我新拷贝的对象修改对象内部的值,并不会影响原有对象的值
另外还要有一点值拷贝,也是赋值,基础数据类型赋值,新修改的数据不会影响原有的数据,但是如果是引用数据类型,那么新拷贝的值修改会影响原有数据
值拷贝(直接赋值操作),主要区分基础数据类型与引用数据类型,如果是基础数据类型,那么新值修改不会影响原有的值,但是如果引用数据类型,那么新修改的值会影响原有数据类型
浅拷贝,如果拷贝的对象内部属性是引用数据类型,那么像es6中的对象扩展符或者Object.assign都是浅拷贝操作,新拷贝的基础数据类型修改不会影响原有值,但是如果拷贝的是引用数据类型,那么新拷贝的值与原有值是同一份引用,新值修改会影响原有的值
Object.assign
深拷贝,一句话,新拷贝的对象修改值不会影响原有值
本文示例code example
The text was updated successfully, but these errors were encountered:
No branches or pull requests
js 的
浅拷贝
与深拷贝
在业务中时常有用到,关于浅拷贝
与深拷贝
的剖析文章层出不穷,本文是笔者对于深拷贝与浅拷贝的理解,一起来夯实 js 语言基础知识的理解吧。正文开始...
在阅读文章之前,本文主要从以下几个方面去探讨
为什么会有浅拷贝与深拷贝
浅拷贝是什么,深拷贝又是什么
浅拷贝与深拷贝有何区别
写一个例子佐证以上所有的观点
为什么会有浅拷贝与深拷贝
我们知道在
js
中基础数据类型是存放在栈内存中的,而引用数据类型是存放在栈地址引用的一个堆内存中。为什么两种数据会存放方式不同?这是一个值的思考的问题?我的猜想,引用数据类型是复杂的数据结构,本质上也是存放栈地址的引用,只是这个地址指向了另外一个堆内存空间,如果他们都是放在一起的话,就不太好区分你是基础数据类型,还是引用数据类型了。首先它们都是拷贝,一个是浅,一个是深,我们先说结论,浅拷贝是基础数据类型的拷贝,只会拷贝一层,如果遇到拷贝的数据是引用数据,那么浅拷贝的数据与原有数据是同一份引用。
而深拷贝是遇到引用数据类型会创建一个新的对象,遍历原有对象,对新对象进行动态赋值,修改新对象引用不影响原有对象的属性值
我们用一个图来解释上面两段比较长的话
基础数据类型直接存放在栈地址内存中,而引用数据类型是存放在栈内存地址的引用中,这个引用实际上指向的区域是一块堆内存空间
在了解
浅拷贝
与深拷贝
之前,我们先来了解下值拷贝
值拷贝
当我对原有基础数据类型与引用数据类型进行
赋值
时用下面代码示例上图
然后运行
node index.js
从执行结果上来看
因此可以得出结论
基础数据类型的赋值,是值的拷贝,会重新开辟一个栈空间,新拷贝的值修改不会影响原有数据类型
引用数据类型的赋值,原有引用数据与新赋值的数据指向的是同一份地址,修改引用数据的属性会影响原来的
以上是两种数据类型值的拷贝,貌似与浅拷贝还有离得有点远
浅拷贝
于是我们看下对象扩展的
浅拷贝
我使用了
es6
对象扩展对原有对象进行拷贝,那么此时结果是怎么样不知道注意到没有,在引用数据类型的第一级如果这个属性是基础数据类型,那么修改并不会影响原有的值,如果属性是引用数据类型,那么这层结构会是一个值拷贝,修改新赋值属性,会影响到原有的对象属性
我们看下图理解下
因此我们可以得出结论,浅拷贝如果遇到基础数据类型,那么修改新值,不会影响原有的值,但是如果数据是引用数据类型,那么新修改的值会影响原有的值,因为新修改的与原修改的是同一份引用。
所以
浅
拷贝只会拷贝一层,如果数据是引用数据类型,实际上会直接引用同一份数据。深拷贝
深拷贝顾名思义,从表现层来看就是,就是为了修改新数据而不影响原有数据而产生的。
我们举个栗子
当我需要修改
userInfo.fav.play1
的值,而不想影响原有userInfo
对象的值,那么此时你就会想到深拷贝,那怎么深拷贝呢。利用
JSON.stringify(data)
拷贝对象结果:
但是我们得考虑到
JSON.stringify
这种有种缺陷,必须是json
对象,有其他比如方法
这种会被自动过滤处理。而且如果json
对象格式错误,就会抛出异常,所以我们看下另外一种方案。使用代理对象思想,将原有对象拷贝一份,然后再赋值
最终结果是
但是如果数据中有数组,貌似数组的这种情况还是同一份值,那是因为直接赋值了
于是需要多加一个条件,需要对数组进行判断
此时结果
以上用一个图来进一步理解下
真是人才,深拷贝原来是这样的
深拷贝与浅拷贝的区别
通过以上例子,我们已经知道
浅拷贝
如果拷贝对象内部的数据是基础数据类型,那么直接拷贝,新对象修改值,不会影响原有的值,如果拷贝的对象是一个引用数据类型,那么会是一个值的引用,此时新拷贝对象修改其值会影响原有的值。浅拷贝
只会拷贝一层,拷贝的内部引用数据类型是同一份。深拷贝
本质上就是无论原对象值是基础数据类型,还是引用数据类型,我新拷贝的对象修改对象内部的值,并不会影响原有对象的值另外还要有一点
值拷贝
,也是赋值,基础数据类型赋值,新修改的数据不会影响原有的数据,但是如果是引用数据类型,那么新拷贝的值修改会影响原有数据总结
值拷贝(直接赋值操作),主要区分基础数据类型与引用数据类型,如果是基础数据类型,那么新值修改不会影响原有的值,但是如果引用数据类型,那么新修改的值会影响原有数据类型
浅拷贝,如果拷贝的对象内部属性是引用数据类型,那么像
es6
中的对象扩展符或者Object.assign
都是浅拷贝操作,新拷贝的基础数据类型修改不会影响原有值,但是如果拷贝的是引用数据类型,那么新拷贝的值与原有值是同一份引用,新值修改会影响原有的值深拷贝,一句话,新拷贝的对象修改值不会影响原有值
本文示例code example
The text was updated successfully, but these errors were encountered: