超详细的 JavaScript 深拷贝实现
此前写过一篇文章:JavaScript 深浅拷贝,其实没那么难!,但里面的拷贝处理显然不够理想。
今天再来详细的讲讲...
一、JSON.stringify() 的缺陷
利用 JavaScript 内置的 JSON 处理函数,可以实现简易的深拷贝:
const obj = { // ... } JSON.parse(JSON.stringify(obj)) // 序列化与反序列化
这个方法,其实能适用于 90% 以上的应用场景。毕竟多数项目下,很少会去拷贝一个函数什么的。
但不得不说,这里面有“坑”,这些“坑”是 JSON.stringify()
方法本身实现逻辑产生的:
JSON.stringify(value[, replacer[, space]])
该方法有以下特点:
- 布尔值、数值、字符串对应的包装对象,在序列化过程会自动转换成其原始值。
undefined
、任意函数
、Symbol 值
,在序列化过程有两种不同的情况。若出现在非数组对象的属性值中,会被忽略;若出现在数组中,会转换成null
。任意函数
、undefined
被单独转换时,会返回undefined
。- 所有
以 Symbol 为属性键的属性
都会被完全忽略,即便在该方法第二个参数replacer
中指定了该属性。 Date 日期
调用了其内置的toJSON()
方法转换成字符串,因此会被当初字符串处理。NaN
和Infinity
的数值及null
都会当做null
。- 这些对象
Map
、Set
、WeakMap
、WeakSet
仅会序列化可枚举的属性。 - 被转换值如果含有
toJSON()
方法,该方法定义什么值将被序列化。 - 对包含
循环引用
的对象进行序列化,会抛出错误。
二、深拷贝的边界
其实,针对以上两个内置的全局方法,还有这么多情况不能处理,是不是很气人。其实不然,我猜测 JSON.parse()
和 JSON.stringify()
只是让我们更方便地操作符合 JSON 格式的 JavaScript 对象或符合 JSON 格式的字符串。
至于上面提到的“坑”,很明显是不符合作为跨平台数据交换的格式要求的。在 JSON 中,它有 null
,是没有 undefined
、Symbol
类型、函数等。
JSON 是一种数据格式,也可以说是一种规范。JSON 是用于跨平台数据交流的,独立于语言和平台。而 JavaScript 对象是一个实例,存在于内存中。JavaScript 对象是没办法传输的,只有在被序列化为 JSON 字符串后才能传输。
此前写过一篇文章,介绍了 JSON 和 JavaScript 的关系以及上述两个方法的一些细节。可看:详谈 JSON 与 JavaScript。
如果自己实现一个深拷贝的方法,其实是有很多边界问题要处理的,至于这些种种的边界 Case,要不要处理最好从实际情况出发。
常见的边界 Case 有什么呢?
主要有循环引用、包装对象、函数、原型链、不可枚举属性、Map/WeakMap、Set/WeakSet、RegExp、Symbol、Date、ArrayBuffer、原生 DOM/BOM 对象等。
**就目前而言,第三方最完善的深拷贝方法是 Lodash 库的 _.cloneDeep()
方法了。在实际项目中,如需处理 JSON.stringify()
无法解决的 Case,我会推荐使用它。**否则请使用内置 JSON 方法即可,没必要复杂化。
但如果为了学习深拷贝,那应该要每种情况都要去尝试实现一下,我想这也是你在看这篇文章的原意。这样,无论是实现特殊要求的深拷贝,还是面试,都可以从容应对。
下面一起来学习吧,如有不足,欢迎指出
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论