[深度克隆]惊呆面试官(doge)!!优雅的对象克隆方式

各位IT界的彦祖,神仙姐姐们中午好。

前提

克隆方式在日常开发中大家都还是使用较多的,毕竟像数组、对象都是引用类型,直接使用一不小心就会造成意料之外的bug。那么今天我们来分享一下对象的克隆方式通常有哪些。

正文

回忆一下,大家在日常开发时需要克隆的时候通常使用什么方式呢,有人会说使用进行转换JSON.parse(JSON.stringify(xxx)),也有人说使用lodash/cloneDeep,更甚者自己封装一段递归方式去实现。相对来说JSON转换是最简单的,但是它不够完美,有一些场景是不能正常转换的。如下:

js 复制代码
<script>
    const obj1 = { a: 'a', b: 'b' } // 正常对象
    const obj2 = { a: 'a', b: [1,2] } // 包含数组对象
    const obj3 = { a: 'a', func: function(){} }  // 函数对象
    const obj4 = { a: 'a', o: { inner: 'inner' } } // 对象对象
    const obj5 = { a: 'a', map: new Map() } // map 对象
    const obj6 = { a: 'a', set: new Set() } // Set 对象
    console.log(JSON.parse(JSON.stringify(obj1))); 
    console.log(JSON.parse(JSON.stringify(obj2))); 
    console.log(JSON.parse(JSON.stringify(obj3))); 
    console.log(JSON.parse(JSON.stringify(obj4))); 
    console.log(JSON.parse(JSON.stringify(obj5))); 
    console.log(JSON.parse(JSON.stringify(obj6))); 
    obj1.c = obj1  // 循环引用对象
    console.log(JSON.parse(JSON.stringify(obj1))); 
  </script>

结果: 我们可以清晰的看到,当对象中包含这些场景时,JSON转换是不正确的

  • 对象中存在 function 时,function 会无法转换
  • 对象中存在 Map | Set 时,会将 Map | Set 转换为普通对象
  • 对象存在循环引用时,那么会直接报错导致程序终止 当然正常情况下,使用JSON转换是没太大问题的,也足以应对大多数情况。

优雅的克隆方式

它是一个构造函数 MessageChannel,通过该构造函数,我们可以实例化一个消息通道,实例中包含了两个属性(port1, port2),表示两个端点,这两个端点之间可以相互传递消息。

js 复制代码
<script>
    const { port1, port2 } = new MessageChannel();
    port1.postMessage('MessageChannel')
    port2.onmessage = mes => {
      console.log(mes);
    }
  </script>

可以看到我们传递过去的内容存在于 mes.data 属性中,现在我们只是传递字符串,那么我们可以传递对象过去。

js 复制代码
const { port1, port2 } = new MessageChannel();
const obj1 = { a: 'a', b: 'b' } // 正常对象
const obj2 = { a: 'a', b: [1,2] } // 包含数组对象
const obj3 = { a: 'a', o: { inner: 'inner' } } // 对象对象
const obj4 = { a: 'a', map: new Map() } // map 对象
const obj5 = { a: 'a', set: new Set() } // Set 对象
port1.postMessage(obj1)
port1.postMessage(obj2)
port1.postMessage(obj3)
port1.postMessage(obj4)
port1.postMessage(obj5)
obj1.c = obj1  // 循环引用对象
port1.postMessage(obj1)
port2.onmessage = mes => {
  console.log(mes.data);
  console.log(obj1 === mes.data);
}

可以清楚地看到,传递过去的对象都能完美的克隆下来,包括 Map | Set 循环引用等,但是注意函数对象是不能使用的,会报错。那么接下来我们就可以将其封装为一个函数。

js 复制代码
const obj = { a: 'a', map: new Map() } // map 对象
function cloneDeep(obj){
  const { port1, port2 } = new MessageChannel();
  port1.postMessage(obj)
  port2.onmessage = mes => {
    console.log(mes.data);
    console.log(obj === mes.data);
  }
}
cloneDeep(obj)

这时我们发现怎么把 mes.data给它 return 出去呢,聪明的彦祖和仙女姐姐们应该想到了, 对!没错。就是Promise。将其改为

js 复制代码
const obj = { a: 'a', map: new Map() } // map 对象
function cloneDeep(obj){
  return new Promise((resolve, reject) => {
    const { port1, port2 } = new MessageChannel();
    port1.postMessage(obj)
    port2.onmessage = mes => {
      resolve(mes.data)
    }
  })
}
cloneDeep(obj).then(res => {
  console.log('res', res);
})

结果如下

大功告成~~

写在最后

当然我们还可以再优化一下,例如做一下异常处理,避免其他人使用时传递了函数对象过来。 这不是最简单的对象克隆方式,但在面试中,如果考察到对象克隆,这无疑是最惊艳面试官的实现方式之一。此外,在日常开发中也能使用到该克隆方式。 再见啦~~

相关推荐
FlyWIHTSKY2 分钟前
Vue 3 onMounted 中控制同步与异步执行策略
前端·javascript·vue.js
蜗牛攻城狮7 分钟前
【Vue3实战】El-Table实现“超过3行省略,悬停显示全文”的完美方案(附性能优化)
前端·vue.js·性能优化·element-plus
孙12~7 分钟前
前端vue3+vite,后端SpringBoot+MySQL
前端·html·学习方法
隔壁小邓9 分钟前
vue的组件化的理解之单独拆分的组件&组件的封装
前端·javascript·vue.js
困惑阿三10 分钟前
全栈部署排雷手册:从 405 报错到飞书推送成功
服务器·前端·后端·nginx·阿里云·node.js·飞书
无名-CODING11 分钟前
从零开始!Vue3+SpringBoot前后端分离项目Docker部署实战(下):Vue前端Nginx反代与致命坑点盘点
前端·spring boot·docker
我命由我1234511 分钟前
Element Plus 问题:选择框表单校验没有触发
开发语言·前端·javascript·html·ecmascript·html5·js
optimistic_chen21 分钟前
【Vue3入门】vue-router 路由管理
前端·javascript·vue.js·路由·router
柯儿的天空36 分钟前
WebGPU全面解析:新一代Web图形与计算API
前端·chrome·microsoft·前端框架·chrome devtools·view design
捕捉一只前端小白38 分钟前
cpolar内网穿透以及微信小程序域名设置
前端·vue.js·微信小程序·小程序