先来看几个小案例:
P1:循环递归法
注:
深拷贝是针对引用类型的, 在进行深拷贝之前, 我们应该先知道js中有哪些引用类型,
js中引用类型目前有六种: object, array, date, regexp,
function, err。 下面的两种方法只能实现object, array的深拷贝。
循环递归法 function、Date、RegExp 和Error无法复制 因为它们有特殊的构造函数。
P2: 循环递归法2
注:
此方法与P1大同小异---对传入的obj进行了解构处理并使用静态方法 Reflect.ownKeys() 返回一个由目标对象自身的属性键组成的数组。Reflect.ownKeys 方法返回一个由目标对象自身的属性键组成的数组
P3:序列化反序列化法实现深拷贝
使用JSON对象的parse和stringify方法来实现深拷贝
注:
它也只能深拷贝对象和数组,对于其他种类的对象,会失真。
这种方法比较适合平常开发中使用,因为通常不需要考虑对象和数组之外的类型。
拷贝的对象的值中如果有函数,undefined,symbol则经过JSON.stringify()序列化后的JSON字符串中这个键值对会消失
无法拷贝不可枚举的属性, 无法拷贝对象的原型链
拷贝Date引用类型会变成字符串
拷贝RegExp引用类型会变成空对象
对象中含有NaN、 Infinity和 - Infinity, 则序列化的结果会变成null
无法拷贝对象的循环应用(即obj[key] = obj)
P4:lodash中深拷贝的实现
注:
Lodash是一个轻量级的JavaScript工具函数库, 它方便了日常开发中对数据的操作, 提高了开发效率。
著名的 lodash 中的 cloneDeep 方法同样是使用 Reflect 法 实现的,
只不过它支持的对象种类更多, 具体的实现过程读者可以参考 lodash 的 baseClone 方法。
lodash可以完成array、 object、 date、 regexp的深拷贝, 但
function 和 error 仍然不可拷贝
https://github.com/lodash/lodash/blob/master/.internal/baseClone.js
日常开发中,通常会对数据,特别是数组和对象进行各种读写等操作:
比如去重,拷贝,合并,过滤,求交集,求和等等。根据平时开发中对数据的操作,
P5:对象成环怎么办?
我们给 test 加一个 loopObj 键,值指向自身:
test.loopObj = test
这时我们使用第一种方法中的 for..in 实现和 Reflect 实现都会栈溢出:
环对象深拷贝报错
而使用第二种方法也会报错:
但 lodash 却可以得到正确结果:
因为 lodash 使用的是栈把对象存储起来了,如果有环对象,就会从栈里检测到,
从而直接返回结果,悬崖勒马。这种算法思想来源于 HTML5 规范定义的结构化克隆算法,
它同时也解释了为什么 lodash 不对 Error 和 Function 类型进行拷贝。
当然,设置一个哈希表存储已拷贝过的对象同样可以达到同样的目的:
这里我们使用 WeakMap 作为哈希表,因为它的键是弱引用的,而我们这个场景里键恰好是对象,需要弱引用。
P6:易混淆点
1、
如果键值不是字符串而是 Symbol,我们会发现一个问题!!!!
会拷贝失败了, 为什么?
原因其实很简单:因为 Symbol 是一种特殊的数据类型, 它最大的特点便是独一无二, 所以它的深拷贝就是浅拷贝
2、
但如果这时我们使用 Reflect 实现的版本:
成功了,因为 for...in 无法获得 Symbol 类型的键,而 Reflect 是可以获取的。
当然,我们改造一下 for...in 实现也可以:
for...in 会追踪原型链上的属性,
而其它三种方法(Object.keys、Reflect.ownKeys 和 JSON 方法)都不会追踪原型链上的属性:
P7:如需要拷贝不可枚举的属性
第四种情况, 就是我们需要拷贝类似属性描述符, setters 以及 getters 这样不可枚举的属性, 一般来说, 这就需要一个额外的不可枚举的属性集合来存储它们。 类似在第二种情况使用
for... in 拷贝 Symbol 类型键时:
我们给 test 变量里的 obj 和 arr 属性定义一下属性描述符:
P8:日常深拷贝,建议序列化反序列化方法。
一、面试时遇见面试官搞事情,写一个能拷贝自身可枚举、自身不可枚举、自身 Symbol 类型键、原型上可枚举、原型上不可枚举、原型上的 Symol 类型键,循环引用也可以拷的深拷贝函数;
二、有特殊需求的深拷贝,建议使用 lodash 的 copyDeep 或 copyDeepWith 方法。
如下:
将之前写的 deepClone 函数封装一下
注:completeAssign这个函数会拷贝所有自有属性的属性描述符,来自于 MDN
欢迎指正!!!