JS对象拷贝

Posted by millzhang on June 14, 2019

浅拷贝和深拷贝

浅拷贝:是拷贝对象的引用,拷贝后的引用都是指向同一个对象的实例,操作会互相影响

深拷贝:在堆中重新分配内存,并且把源对象所有属性都进行新建拷贝,以保证深拷贝的对象的引用图不包含任何原有对象或对象图上的任何对象,拷贝后的对象与原来的对象是完全隔离,互不影响;

浅拷贝

1
2
3
4
5
var a = {c:1};
var b = a;
console.log(a === b);//输出true
a.c = 2;
console.log(b.c);//输出2

源对象拷贝实例,其属性对象拷贝应用

这种情况下,外层源对象是拷贝实例,如果其属性元素为复杂数据类型时,内层元素拷贝引用。

对于对象实例的拷贝,常用的方法有:Array.prototype.slice(), Array.prototype.concat(), jQuery的$.extend({},obj)

深拷贝

深拷贝后两个对象元素,完全独立,互不干扰。

常见的方法有:

  • JSON.parse()
  • JSON.stringify()
  • $.extend(true,{},obj)
  • lodash_.cloneDeep_.clone(value,true)

但是,通过JSON.parse来深拷贝对于包含function正则属性的对象不适用!(无法解析function,正则解析直接报错)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let a = {
	name: "lisi",
	atts: [1, 2, 3],
	child: [{ name: "miao" }, { name: "jiao" }],
	 max: function() {
	  return "max";
	}
};
let a_str = JSON.stringify(a);
let b = JSON.parse(a_str);
a.name = "lucy";
b.name = "hanmeimei";
setTimeout(() => {
	console.log(a, b); // 输出互不影响
}, 0);

enter description here

解决function问题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let clone_deep = function(obj) {
	if (obj === null) return null;
	if (typeof obj !== "object") return obj;
	if (typeof obj === RegExp) return new RegExp(obj);
	var newObj = new obj.constructor(); //构造空对象,保持继承链
	for (var key in obj) {
	  if (obj.hasOwnProperty(key)) {
		//不遍历其原型链上的属性
		var val = obj[key];
		// 使用arguments.callee解除与函数名的耦合
		newObj[key] = typeof val === "object" ? arguments.callee(val) : val;
	  }
	}
	return newObj;
  };
let b = clone_deep(a);
console.log(a.max == b.max); // true,函数的的引用还是指向同一个
console.log(a, b);

上述方法还是不行,再深究就会像参考文章那样,深了去了; 换个思路:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 递归,深拷贝对象和数组
function deepClone(obj) {
    if (!isObject(obj)) {
        throw new Error('obj 不是一个对象!')
    }

    let isArray = Array.isArray(obj)
    let cloneObj = isArray ? [] : {}
    for (let key in obj) {
        cloneObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key]
    }

    return cloneObj
}

// 代理法
function deepClone(obj) {
    if (!isObject(obj)) {
        throw new Error('obj 不是一个对象!')
    }

    let isArray = Array.isArray(obj)
    let cloneObj = isArray ? [...obj] : { ...obj }
    Reflect.ownKeys(cloneObj).forEach(key => {
        cloneObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key]
    })

    return cloneObj
}

上述两种方法效果一样,基本类型都能深拷贝,但function、date、reg、err并没有复制成功,因为他们有特殊的构造函数。

lodash的深拷贝原理类似,只是支持更多类型,比如:date,reg

总结

综上所述吧,要实现完全的深拷贝还是蛮复杂的,实际使用场景也不多。 所以,简单的深拷贝可以使用JSON的序列和反序列化完成。 但深拷贝是对数据类型、原型链掌握上的一个完整的考察,需要考虑的场景比较多。


【参考】

  • https://blog.csdn.net/yaodebian/article/details/82778403
  • https://www.jianshu.com/p/b08bc61714c7