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
已经有很多关于深拷贝与浅拷贝的文章,为什么自己还要写一遍呢💯
深拷贝与浅拷贝
学习就好比是座大山,人们沿着不同的路登山,分享着自己看到的风景。你不一定能看到别人看到的风景,体会到别人的心情。只有自己去登山,才能看到不一样的风景,体会才更加深刻。
分享一个不错的思维导图👇
通过文本的总结,希望可以明白:
本章节直接从拷贝开始说起,对于基本数据类型,引用数据类型之前的区别,可以看看上面的思维导图👆
或者看看我之前的章节补一补基础
对于引用数据类型的话,细分可以分为下面三个方面
引用类型的赋值是传址。只是改变指针的指向,例如,引用类型的赋值是对象保存在栈中的地址的赋值,这样的话两个变量就指向同一个对象,因此两者之间操作互相有影响。例如:
var a = {}; // a保存了一个空对象的实例 var b = a; // a和b都指向了这个空对象 a.name = 'jozo'; console.log(a.name); // 'jozo' console.log(b.name); // 'jozo' b.age = 22; console.log(b.age);// 22 console.log(a.age);// 22 console.log(a == b);// true
这样子的情况,会导致a和b指向同一份数据,对其中一个进行修改数据的话,会影响到另外一个,实际开发中,这不是我们预期中的结果,这会照成某种程度上的bug。
那么我们如何不让相互之间产生影响呢?一种简单的办法就是拷贝一份a变量的数据,所以根据拷贝的层次不同可以分为浅拷贝和深拷贝,浅拷贝的话知识进行一层拷贝,深拷贝的话是无限层次的拷贝!
我们先来实现一个浅拷贝
let shallowClone = source => { let target = {} for(let i in source) { if( source.hasOwnProperty(i) ) target[i] = source[i]; } return target } let demo = { b:{ c : { } } } let demo2 = shallowClone(demo) let demo3 = demo; console.log(demo3 === demo ) // true console.log(demo2.b.c === demo.b.c ) // true console.log(demo2.b === demo.b ) // true console.log(demo2 === demo ) // false
demo3 = demo 赋值的话,是地址的赋值,也就是说指向同一个对象,那么不是我们想要的结果,我们来看看shallowClone函数,这个是浅拷贝的一种实现方式,那么demo2变量应该就是实现了一层的拷贝,正如20行效果,demo2变量是在堆中开了一个新内存,所以两者指向不同对象,demo2.b === demo.b 为 true 说明 这就是浅拷贝效果,简单的拷贝一层,那么我们是不是可以递归的思想去完成深拷贝呢?
Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
Object.assign()
let demo = { name : 'dayday', book : { title : 'Do you really Know JS', price : "45" } } let clone_demo = Object.assign({}, demo) console.log(clone_demo); demo.name = 'new name' demo.book.price = '100' console.log(clone_demo.name,clone_demo.book.price); // dayday 100
修改上面代码demo变量之后,对象clone_demo基本属性没有改变,但是修改demo对象中book引用属性时,对象clone_demo相应位置属性值也发生改变,同样的接下来展开运算符也是一样效果👇
let demo = { name : 'dayday', book : { title : 'Do you really Know JS', price : "45" } } let clone_demo = {...demo} console.log(clone_demo); demo.name = 'new name' demo.book.price = '100' console.log(clone_demo.name,clone_demo.book.price); // dayday 100
我们可以看到展开运算… 效果跟Object.assign() 效果是一样的。
slice() 方法返回一个新的数组对象,这一对象是一个由 begin和 end(不包括end)决定的原数组的浅拷贝。原始数组不会被改变。
slice()
begin
end
let a = [0, "1", [2, 3]]; let b = a.slice(1); console.log(b); // ["1", [2, 3]] a[1] = "99"; a[2][0] = 4; console.log(a); // [0, "99", [4, 3]] console.log(b); // ["1", [4, 3]]
可以看出,改变 a[1] 之后 b[0] 的值并没有发生变化,但改变 a[2][0] 之后,相应的 b[1][0] 的值也发生变化。说明 slice() 方法是浅拷贝,相应的还有concat等,在工作中面对复杂数组结构要额外注意。
a[1]
b[0]
a[2][0]
b[1][0]
concat
深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。拷贝前后两个对象互不影响。
let demo = { name : 'dayday', book : { title : 'Do you really Know JS', price : "45" } } let clone_demo = JSON.parse(JSON.stringify(demo)) console.log(clone_demo); demo.name = 'new name' demo.book.price = '100' console.log(clone_demo.name,clone_demo.book.price); // dayday 45
完全改变变量 demo 之后对 clone_demo 没有任何影响,这就是深拷贝的魔力。
同样的对于数组使用该方法也是可以达到深拷贝的。
对于undefined symbol 函数三种情况会直接忽略
undefined
symbol
函数
let demo = { name : 'dayday', h1 : undefined, h2 : Symbol('dayday'), h3 : function () {}, } let clone_demo = JSON.parse(JSON.stringify(demo)) console.dir(clone_demo) // { name : 'dayday' }
循环引用情况下,会报错。
let obj = { a: 1, b: { c: 2, d: 3 } } obj.a = obj.b; obj.b.c = obj.a; let b = JSON.parse(JSON.stringify(obj)); // Uncaught TypeError: Converting circular structure to JSON
new Date 情况下,转换结果不正确。
new Date
new Date(); // Wed Jul 01 2020 16:19:07 GMT+0800 (中国标准时间) {} JSON.stringify(new Date()); // ""2020-07-01T08:19:19.860Z"" JSON.parse(JSON.stringify(new Date())); // "2020-07-01T08:19:35.569Z"
解决方法转成字符串或者时间戳就好了
let date = (new Date()).valueOf(); // 1593591638596 JSON.stringify(date); // "1593591638596" JSON.parse(JSON.stringify(date)); // 1593591638596
正则情况下
let demo = { name: "daydaylee", a: /'123'/ } console.log(demo); // {name: "daydaylee", a: /'123'/} let clone_demo = JSON.parse(JSON.stringify(obj)); console.log(clone_demo); // {name: "daydaylee", a: {}}
PS:为什么会存在这些问题可以学习一下 JSON
除了上面介绍的深拷贝方法,常用的还有jQuery.extend() 和 lodash.cloneDeep(),由于文章篇幅的问题,这里就不多介绍了,有兴趣的可以自己去了解了解
jQuery.extend()
lodash.cloneDeep()
面试官叫你实现一个深拷贝的话,你只要记得浅拷贝+递归,浅拷贝的时候,去判断是不是一个对象就行的,是对象的话,就进行递归操作。
之前的简单浅拷贝:
let shallowClone = source => { let target = {} for(let key in source) { if(Object.prototype.hasOwnProperty.call(source, key)){ target[key] = typeof source[key] === 'object' ? shallowClone(source[key]) : source[key]; } } return target } let demo = { name : 'dayday', book : { title : 'Do you really Know JS', price : "45" } } let clone_demo = shallowClone(demo); console.log(clone_demo); demo.name = 'new name' demo.book.price = '100' console.log(clone_demo.name,clone_demo.book.price) // dayday 45
写到这里,至少一个简单的深克隆实现了,但是还是有些问题没有解决!
typeof null === object
首先的写一个兼容数组并且判断null方法的函数
let isObject = obj => typeof obj === 'object' && obj !== null ;
那么进一步完善了深度拷贝的方法
// 保留数组 并且判断是不是null let isObject = obj => typeof obj === 'object' && obj !== null ; let shallowClone2 = source => { if(!isObject(source)) return source // 非对象返回自身 let target = Array.isArray(source) ? [] : {} for(let key in source) { if(Object.prototype.hasOwnProperty.call(source, key)){ target[key] = isObject(source[key]) ? shallowClone2(source[key]) : source[key]; } } return target } let demo = { name : 'dayday', book : { title : 'Do you really Know JS', price : "45" }, h1 : null, h2 : [1,2,3], h3 : undefined } let clone_demo = shallowClone2(demo); console.log(clone_demo); demo.name = 'new name' demo.book.price = '100' demo.h2[1] = 'new data' console.log(clone_demo.name,clone_demo.book.price) // dayday 45 console.log(clone_demo); // 修改demo值为能影响clone_demo
这篇文章写的很好:深拷贝的终极探索(99%的人都不知道)
它还对深度拷贝有了新的优化,比如JSON.parse(JSON.stringify(obj))循环引用抛出异常的问题,做出了优化,那我们试着去优化这个小问题。
对于循环检测的话,我们可以使用哈希检测的方法,比如设置一个数组或者是已经拷贝的对象,当检测到对象已经存在哈希表时,就取出该值🤭
let isObject = obj => typeof obj === 'object' && obj !== null; let shallowClone3 = (source, hash = new WeakMap()) => { if (!isObject(source)) return source // 非对象返回自身 if (hash.has(source)) return hash.get(source) // 新增检测, 查哈希表 let target = Array.isArray(source) ? [] : {} hash.set(source, target) // 设置哈希表值 for (let key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = isObject(source[key]) ? shallowClone3(source[key], hash) : source[key]; // 传入哈希表 } } return target } let obj = { a: 1, b: { c: 2, d: 3 } } obj.a = obj.b; obj.b.c = obj.a; let clone_obj = shallowClone3(obj) console.log(clone_obj)
写完这段代码的话,至少面试实现一个这样子的深拷贝马马虎虎过的去,当然了还是有很多的问题需要解决的:
当然了有兴趣的读者可以深入的了解呐🚀
JavaScript深拷贝的一些坑
面试题之如何实现一个深拷贝
深入剖析 JavaScript 的深复制
MDN展开语法
MDN之Object.assign()
The text was updated successfully, but these errors were encountered:
daydaylee1227
No branches or pull requests
前言
已经有很多关于
深拷贝与浅拷贝
的文章,为什么自己还要写一遍呢💯分享一个不错的思维导图👇
通过文本的总结,希望可以明白:
本章节直接从拷贝开始说起,对于基本数据类型,引用数据类型之前的区别,可以看看上面的思维导图👆
或者看看我之前的章节补一补基础
引用数据类型拷贝
对于引用数据类型的话,细分可以分为下面三个方面
赋值
引用类型的赋值是传址。只是改变指针的指向,例如,引用类型的赋值是对象保存在栈中的地址的赋值,这样的话两个变量就指向同一个对象,因此两者之间操作互相有影响。例如:
这样子的情况,会导致a和b指向同一份数据,对其中一个进行修改数据的话,会影响到另外一个,实际开发中,这不是我们预期中的结果,这会照成某种程度上的bug。
那么我们如何不让相互之间产生影响呢?一种简单的办法就是拷贝一份a变量的数据,所以根据拷贝的层次不同可以分为浅拷贝和深拷贝,浅拷贝的话知识进行一层拷贝,深拷贝的话是无限层次的拷贝!
我们先来实现一个浅拷贝
demo3 = demo 赋值的话,是地址的赋值,也就是说指向同一个对象,那么不是我们想要的结果,我们来看看shallowClone函数,这个是浅拷贝的一种实现方式,那么demo2变量应该就是实现了一层的拷贝,正如20行效果,demo2变量是在堆中开了一个新内存,所以两者指向不同对象,demo2.b === demo.b 为 true 说明 这就是浅拷贝效果,简单的拷贝一层,那么我们是不是可以递归的思想去完成深拷贝呢?
浅拷贝的实现方式
Object.assign()
Object.assign()
方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。修改上面代码demo变量之后,对象clone_demo基本属性没有改变,但是修改demo对象中book引用属性时,对象clone_demo相应位置属性值也发生改变,同样的接下来展开运算符也是一样效果👇
展开运算符...
我们可以看到展开运算… 效果跟Object.assign() 效果是一样的。
Array.prototype.slice()
slice()
方法返回一个新的数组对象,这一对象是一个由begin
和end
(不包括end
)决定的原数组的浅拷贝。原始数组不会被改变。可以看出,改变
a[1]
之后b[0]
的值并没有发生变化,但改变a[2][0]
之后,相应的b[1][0]
的值也发生变化。说明slice()
方法是浅拷贝,相应的还有concat
等,在工作中面对复杂数组结构要额外注意。深拷贝实现方式
深拷贝会拷贝所有的属性,并拷贝属性指向的动态分配的内存。当对象和它所引用的对象一起拷贝时即发生深拷贝。深拷贝相比于浅拷贝速度较慢并且花销较大。拷贝前后两个对象互不影响。
JSON.parse(JSON.stringify(obj))
完全改变变量 demo 之后对 clone_demo 没有任何影响,这就是深拷贝的魔力。
同样的对于数组使用该方法也是可以达到深拷贝的。
注意的就是:
对于
undefined
symbol
函数
三种情况会直接忽略循环引用情况下,会报错。
new Date
情况下,转换结果不正确。解决方法转成字符串或者时间戳就好了
正则情况下
PS:为什么会存在这些问题可以学习一下 JSON
除了上面介绍的深拷贝方法,常用的还有
jQuery.extend()
和lodash.cloneDeep()
,由于文章篇幅的问题,这里就不多介绍了,有兴趣的可以自己去了解了解面试如何实现一个深拷贝
面试官叫你实现一个深拷贝的话,你只要记得浅拷贝+递归,浅拷贝的时候,去判断是不是一个对象就行的,是对象的话,就进行递归操作。
之前的简单浅拷贝:
写到这里,至少一个简单的深克隆实现了,但是还是有些问题没有解决!
typeof null === object
首先的写一个兼容数组并且判断null方法的函数
那么进一步完善了深度拷贝的方法
这篇文章写的很好:深拷贝的终极探索(99%的人都不知道)
它还对深度拷贝有了新的优化,比如JSON.parse(JSON.stringify(obj))循环引用抛出异常的问题,做出了优化,那我们试着去优化这个小问题。
对于循环检测的话,我们可以使用哈希检测的方法,比如设置一个数组或者是已经拷贝的对象,当检测到对象已经存在哈希表时,就取出该值🤭
写完这段代码的话,至少面试实现一个这样子的深拷贝马马虎虎过的去,当然了还是有很多的问题需要解决的:
当然了有兴趣的读者可以深入的了解呐🚀
总结
参考
JavaScript深拷贝的一些坑
面试题之如何实现一个深拷贝
深入剖析 JavaScript 的深复制
MDN展开语法
MDN之Object.assign()
深拷贝的终极探索(99%的人都不知道)
The text was updated successfully, but these errors were encountered: