在 JS 中, 一般的 = 号传递的都是对象 / 数组的引用, 并没有真正地拷贝一个对象, 那如何进行对象的深度拷贝呢? 如果你对此也有疑问, 这篇文章或许能够帮助到你
一, 对象引用, 浅层拷贝与深层拷贝的区别
JS 的对象引用传递理解起来很简单, 参考如下代码:
- var a = {
- name:'wanger'
- }
- var b = a ;
- a===b // true
- b.name = 'zhangsan'
- a.name //'zhangan'
上述代码中, 使用了 = 进行赋值, 于是 b 指向了 a 所指向的栈的对象, 也就是 a 与 b 指向了同一个栈对象, 所以在对 b.name 赋值时, a.name 也发生了变化. 为了避免上面的情况, 可以对对象进行拷贝, 代码如下:
- var a = {
- name:'wanger'
- }
- var b = Object.assign({
- }, a)
- a===b // false
- b.name = 'zhangsan'
- a.name //'wanger'
上面代码将原始对象拷贝到一个空对象, 就得到了原始对象的克隆, 这时候 a 与 b 指向的是不同的栈对象, 所以对 b.name 重新复制也不会影响到 a.name. 但是如果 a.name 是一个对象的引用, 而不是一个字符串, 那么上面的代码也会遇到一些问题, 参考如下代码:
- var a = {
- name:{
- firstName:'wang',lastName:'er'
- }
- }
- var b = Object.assign({
- }, a)
- a===b // false
- b.name.firstName = 'zhang'
- a.name.firstName //'zhang'
b.name.firstName 又影响到了 a.name.firstName, 这是因为 Object.assign()方法只是浅层拷贝, a.name 是一个栈对象的引用, 赋值给 b 时, b.name 也同样是这个栈对象的引用, 很多时候, 我们不想让这种事情发生, 所以我们就需要用到对象的深拷贝.
二, 使用 JSON.parse()与 JSON.stringify()对对象进行拷贝
通常情况下, 我们可以使用 JSON.parse()与 JSON.stringify()实现对象的深克隆, 如下:
- var clone = function (obj) {
- return JSON.parse(JSON.stringify(obj));
- }
这种方法只适用于纯数据 JSON 对象的深度克隆, 因为有些时候, 这种方法也有缺陷, 参考如下代码:
- var clone = function (obj) {
- return JSON.parse(JSON.stringify(obj));
- }
- var a = {a:function(){console.log('hello world')},b:{c:1},c:[1,2,3],d:"wanger",e:new Date(),f:null,g:undefined}
- var b = clone(a)
打印如下:
我们发现, 上述的方法会忽略值为 function 以及 undefied 的字段, 而且对 date 类型的支持也不太友好.
更要紧的是, 上述方法只能克隆原始对象自身的值, 不能克隆它继承的值, 参考如下代码:
- function Person (name) {
- this.name = name
- }
- var wanger = new Person('王二')
- var newwanger = clone(wanger)
- wanger.constructor === Person // true
- newwanger.constructor === Object // true
打印如下:
我们发现, 克隆的对象的构造函数已经变成了 Object, 而原来的对象的构造是 Person.
三, 目前没有发现 bug 的对象深拷贝方法
王二在网上参考了不少文章, 方法都不尽完美, 于是在前人基础上改造了一下, 方法如下, 目前没有发现有什么 bug:
- var clone = function (obj) {
- if(obj === null) return null
- if(typeof obj !== 'object') return obj;
- if(obj.constructor===Date) return new Date(obj);
- var newObj = new obj.constructor (); // 保持继承链
- for (var key in obj) {
- if (obj.hasOwnProperty(key)) { // 不遍历其原型链上的属性
- var val = obj[key];
- newObj[key] = typeof val === 'object' ? arguments.callee(val) : val; // 使用 arguments.callee 解除与函数名的耦合
- }
- }
- return newObj;
- };
这里有三点需要注意:
1, 用 new obj.constructor ()构造函数新建一个空的对象, 而不是使用 {} 或者[], 这样可以保持原形链的继承;
2, 用 obj.hasOwnProperty(key)来判断属性是否来自原型链上, 因为 for..in.. 也会遍历其原型链上的可枚举属性.
3, 上面的函数用到递归算法, 在函数有名字, 而且名字以后也不会变的情况下, 这样定义没有问题. 但问题是这个函数的执行与函数名 factorial 紧紧耦合在了一起. 为了消除这种紧密耦合的现象, 需要使用 arguments.callee.
2017-10-03 添加, 之前没有考虑正则对象的问题, 这里再做一下修改:
- var clone = function (obj) {
- if(obj === null) return null
- if(typeof obj !== 'object') return obj;
- if(obj.constructor===Date) return new Date(obj);
- if(obj.constructor === RegExp) return new RegExp(obj);
- var newObj = new obj.constructor (); // 保持继承链
- for (var key in obj) {
- if (obj.hasOwnProperty(key)) { // 不遍历其原型链上的属性
- var val = obj[key];
- newObj[key] = typeof val === 'object' ? arguments.callee(val) : val; // 使用 arguments.callee 解除与函数名的耦合
- }
- }
- return newObj;
- };
来源: http://www.bubuko.com/infodetail-3035293.html