前言
此前聊过深浅拷贝问题面试官:手写一个浅拷贝和一个深拷贝(拷贝详解) – 掘金 (juejin.cn)
但是关于深拷贝的解释不够详细,故出此期文章
拷贝指的是将一个引用类型的元素复制到一个新的对象中
拷贝又分为深拷贝和浅拷贝,浅拷贝只拷贝对象的引用地址,深拷贝会层层拷贝每个属性值,不受原对象修改值的影响
大白话理解:浅拷贝拷贝得不深,副本会受原对象的影响,深拷贝拷贝的深,副本不受原对象的影响
浅拷贝常见方法:
Object.assign
解构
concat
slice
深拷贝常见方法:
JSON.parse(JSON.stringify)
structuredClone
JSON.parse(JSON.stringify)
这个方法仅仅是刚好有深拷贝的效果,先将对象转换成
JSON
格式,又将JSON
格式转换成js对象
已经看到了它的深拷贝效果,但是这个方法是有缺陷的
它拷贝BigInt
大整型会报错,可能因为太大了吧doge
另外无法拷贝Symbol
、function
、undefined
、null
无法拷贝
Symbol
其实合情合理,Symbol
的含义就是独一无二的值,怎么能被复制?
它还不能拷贝对象的循环引用
看下面这个🌰,这么写就相当于obj.a.b
又被赋值成了 obj.a
,成了一个环
总结
缺陷:
无法拷贝BigInt
(报错)、对象的循环引用
(报错)、Symbol
、function
、undefined
structuredClone
这个方法是官方在2021年打造的structuredClone() – Web API 接口参考 | MDN (mozilla.org)
以前js深拷贝只能用JSON
那个方法,但是近几年官方推出了个这个新的方法专门用于深拷贝
这个方法的实现原理就是用的postMessage
通信实现的,postMessage
可以用来解决跨域
此前跨域文章提到过详谈跨域 – 掘金 (juejin.cn)
先看下不能拷贝什么
Symbol
无法拷贝,合理
嚯~函数也不能拷贝
其实就这两个东西不能拷贝,其余都可以,包括JSON
无法拷贝的BigInt
、undefined
、对象循环引用
已经非常完美了,symbol
无法拷贝合情合理,但是函数为何不拷贝,我也不理解
总结
缺陷:
无法拷贝Symbol
和function
(均会报错)
手写拷贝函数
既然官方打造的structuredClone
无法拷贝函数,那就自己手写一个,正好,前阵子面试就被问到过这个问题,当时的想法就是先把函数转成字符串,然后再转回来
当然实际情况还需要考虑到this
指向,因此需要用call
掰一下
let fn = function () {
console.log('hello world')
}
function copy (fn) {
let foo = fn.toString()
return new Function(`return ${foo}`).call(fn)
}
let foo = copy(fn)
foo() // hello world
我们直接写一个new Function
会得到一个匿名函数,如果往里面传值,里面的值会放入到函数体内,因此只要再return
一下就可以把fn
拿出来调用,这才有了上面的写法
手写递归深拷贝
function deepCopy(obj) {
const objCopy = Array.isArray(obj) ? [] : {}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
if (obj[key] instanceof Object) {
objCopy[key] = deepCopy(obj[key])
} else {
objCopy[key] = obj[key]
}
}
}
return objCopy
}
注意,这里拿到key
,一定不能.key
,得是[key]
,否则副本身上复制到的key
真就是key
了,用中括号拿到key
就是当成变量,用点拿到key
就是当成字符串,当然用中括号也可以拿到字符串,自行添加一个字符串即可
另外,如果遍历到的value
发现还是引用类型,那么就递归,递归得到的东西再return
掉即可
遍历对象一般用for in
,而不是for of
,for of
只能遍历具有迭代起属性的对象,普通对象没有,但是for in
这个方法有个缺陷,就是会遍历得很深,可以拿到对象原型身上的方法,通常情况下我们不会去拷贝人家原型身上的方法,因此需要用hasOwnProperty
这个方法来隔绝掉,这个方法返回布尔值,对象身上显示具有的属性才为true
,不具有的或者隐式具有(原型上)均为false
MessageChannel
管道通信MessageChannel – Web API 接口参考 | MDN (mozilla.org)
这个方法和
JSON
类似,不是用来做深拷贝的,只不过刚好可以这样被处理成深拷贝的效果
这个方法会创建一个隧道,这样就有两个端口,在其中一个端口喊话,另一个端口就可以收到
这个方法是个异步方法,也就是new MessageChannel
需要等待,我用await
去解决这个异步,既然用上了await
,await
右边的函数得是promise
才行,因此写成promise
的形式
但是这个方法既然是通信,所以你运行完需要终止它,并且只能手动终止
let obj = {
a: {
b: 1
}
}
function deepCopy(obj) {
return new Promise((resolve) => {
const { port1, port2 } = new MessageChannel() // 对象解构的key不能乱写
port1.postMessage(obj) // 喊话obj
obj.a.b = 2 // 测试深拷贝
port2.onmessage = function (msg) { // msg就是port1喊话的内容
resolve(msg.data) // msg被包裹了一层data
}
})
}
async function fn () {
let objCopy = await deepCopy(obj)
console.log(objCopy);
}
fn() // { a: { b: 1 } }
最后
知道管道通信的人不多,面试官要是问你深拷贝的实现,你还能把这个方法讲给面试官听,面试官肯定非常满意
如果你对春招感兴趣,可以加我的个人微信:
Dolphin_Fung
,我和我的小伙伴们有个面试群,可以进群讨论你面试过程中遇到的问题,我们一起解决
另外有不懂之处欢迎在评论区留言,如果觉得文章对你学习有所帮助,还请”点赞+评论+收藏“一键三连,感谢支持!
原文链接:https://juejin.cn/post/7352717985021083658 作者:Dolphin_海豚