前言
对于前端同学来说,对象的深拷贝
可以说是面试
中最火热的题目之一了,今天我们一起来把它盘明白。
先来理解下对象深拷贝
的概念:
深拷贝是指创建一个对象,这个对象的内容和原始对象完全相同
,但它们是存储在不同的内存地址
上的,这意味着,我们修改新对象,原始对象不受影响
。
其中有两个关键点:
- 对象是以
key
和value
键值对的方式存储的,所以要拷贝它们必须要用循环
- 既然要
深拷贝
,相较于只拷贝最外层
的浅拷贝
,就需要用递归或循环
拷贝N层
对象深拷贝的方式
JSON.parse(JSON.stringify(obj))
JSON.stringify
:将一个对象序列化成一个JSON字符串,包括嵌套的对象属性JSON.parse
:将一个JSON反序列化成为一个js对象
由于在内存中 JSON 字符串的地址都是独立的,和原始对象不是同一个地址,所以我们就能通过JSON.parse解析出一个新对象了。
下面看一下用这种方式实现深拷贝的优缺点:
优点:
- 简单易用:语法
JSON.parse(JSON.stringify(obj))
,用起来非常简单 - 跨平台:在不同平台和环境都能用
- 兼容性好:各大浏览器都支持
缺点:
- 无法处理特殊对象类型,比如函数、正则表达式、日期对象等
- 拷贝的时候会丢失函数和undefined
- 时间对象会变成字符串形式
- RegExp、Error 对象会变成空对象
- NaN、Infinity、-Infinity会变成 null
- 等等...
- 无法处理循环引用,比如在一个对象中,a引用了b,b引用了c,而c又引用了a,出现这种情况调用
JSON.parse(JSON.stringify(obj))
会报错
借助第三方库
实现深拷贝,一般我们会借助第三方库实现,比如lodash
,lodash提供了一个cloneDeep
的方法实现深拷贝
const _ = require('lodash')
const obj = { a: [{ b: 2 }] }
const res = _.cloneDeep(obj)
console.log(res)
// 输出:{ a: [ { b: 2 } ] }
深拷贝的实现
深拷贝其实实现起来要写完整,还是挺复杂的,要处理函数
、数组
、正则
,甚至是symbol
、buffer
等,但对于面试来说,我们写个简单版本就行啦。
废话不多说,直接上完整代码。
const isObj = (target) => typeof target === 'object' && target !== null
function deepClone(obj, hash = new WeakMap()) {
if (!isObj(obj)) return obj
if (hash.has(obj)) return has.get(obj)
const target = new obj.constructor()
hash.set(obj, target)
Object.keys(obj).forEach((key) => {
target[key] = deepClone(obj[key], hash)
})
return target
}
const obj = { a: [{ b: 2 }] }
const res = deepClone(obj)
console.log(res)
// 输出:{ a: [ { b: 2 } ] }
我们用一个WeakMap
来处理循环引用
,然后通过拿到对象引用的constructor
来复制对象,这样我们就省去了判断不同对象类型这一步,会简单很多,然后forEach
循环递归复制就好啦。
虽然可能有人会说用constructor
比较粗糙,但这是比较简洁的写法,我们面试的时候大可不必这么较真,只要知道它的核心思路和原理就行啦!