让面试官眼前一亮的深拷贝

前言

此前聊过深浅拷贝问题面试官:手写一个浅拷贝和一个深拷贝(拷贝详解) – 掘金 (juejin.cn)

但是关于深拷贝的解释不够详细,故出此期文章

拷贝指的是将一个引用类型的元素复制到一个新的对象中

拷贝又分为深拷贝和浅拷贝,浅拷贝只拷贝对象的引用地址,深拷贝会层层拷贝每个属性值,不受原对象修改值的影响

大白话理解:浅拷贝拷贝得不深,副本会受原对象的影响,深拷贝拷贝的深,副本不受原对象的影响

浅拷贝常见方法:

  1. Object.assign
  2. 解构
  3. concat
  4. slice

深拷贝常见方法:

  1. JSON.parse(JSON.stringify)
  2. structuredClone

JSON.parse(JSON.stringify)

这个方法仅仅是刚好有深拷贝的效果,先将对象转换成JSON格式,又将JSON格式转换成js对象

让面试官眼前一亮的深拷贝

已经看到了它的深拷贝效果,但是这个方法是有缺陷的

它拷贝BigInt大整型会报错,可能因为太大了吧doge

让面试官眼前一亮的深拷贝

另外无法拷贝Symbolfunctionundefinednull

无法拷贝Symbol其实合情合理,Symbol的含义就是独一无二的值,怎么能被复制?

让面试官眼前一亮的深拷贝

它还不能拷贝对象的循环引用

看下面这个🌰,这么写就相当于obj.a.b 又被赋值成了 obj.a,成了一个环

让面试官眼前一亮的深拷贝

总结

缺陷:

无法拷贝BigInt(报错)、对象的循环引用(报错)、Symbolfunctionundefined

structuredClone

这个方法是官方在2021年打造的structuredClone() – Web API 接口参考 | MDN (mozilla.org)

以前js深拷贝只能用JSON那个方法,但是近几年官方推出了个这个新的方法专门用于深拷贝

这个方法的实现原理就是用的postMessage通信实现的,postMessage可以用来解决跨域

此前跨域文章提到过详谈跨域 – 掘金 (juejin.cn)

先看下不能拷贝什么

让面试官眼前一亮的深拷贝

Symbol无法拷贝,合理

让面试官眼前一亮的深拷贝

嚯~函数也不能拷贝

其实就这两个东西不能拷贝,其余都可以,包括JSON无法拷贝的BigIntundefined对象循环引用

让面试官眼前一亮的深拷贝

已经非常完美了,symbol无法拷贝合情合理,但是函数为何不拷贝,我也不理解

总结

缺陷:

无法拷贝Symbolfunction(均会报错)

手写拷贝函数

既然官方打造的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 offor of只能遍历具有迭代起属性的对象,普通对象没有,但是for in这个方法有个缺陷,就是会遍历得很深,可以拿到对象原型身上的方法,通常情况下我们不会去拷贝人家原型身上的方法,因此需要用hasOwnProperty这个方法来隔绝掉,这个方法返回布尔值,对象身上显示具有的属性才为true,不具有的或者隐式具有(原型上)均为false

MessageChannel

管道通信MessageChannel – Web API 接口参考 | MDN (mozilla.org)

这个方法和JSON类似,不是用来做深拷贝的,只不过刚好可以这样被处理成深拷贝的效果

这个方法会创建一个隧道,这样就有两个端口,在其中一个端口喊话,另一个端口就可以收到

这个方法是个异步方法,也就是new MessageChannel需要等待,我用await去解决这个异步,既然用上了awaitawait右边的函数得是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_海豚

(0)
上一篇 2024年4月1日 下午4:10
下一篇 2024年4月1日 下午4:21

相关推荐

发表回复

登录后才能评论