五、JavaScript专题之深浅拷贝

本文主要记录一下深拷贝和浅拷贝的一些常用实现方式。

浅拷贝和深拷贝都只针对于引用数据类型,浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存;但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象;
区别:浅拷贝只复制对象的第一层属性、深拷贝可以对对象的属性进行递归复制;

浅拷贝

常用方式:

  1. Object.assign 方法
1
2
3
4
var obj = { a: 1, b: 2 };
var obj1 = Object.assign({}, obj);
obj1.a = 3;
console.log(obj.a); //1
  1. for in 方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 只复制第一层的浅拷贝
function simpleCopy(obj1) {
var obj2 = Array.isArray(obj1) ? [] : {};
for (let i in obj1) {
obj2[i] = obj1[i];
}
return obj2;
}
var obj1 = {
a: 1,
b: 2,
c: {
d: 3,
},
};
var obj2 = simpleCopy(obj1);
obj2.a = 3;
obj2.c.d = 4;
console.log(obj1); // {a:1, b:2, c:{d:4}}

除了以上方法外,数组还有 slice, concat, 以及 ES6 提供的“…”运算符。当然这不是重点,重点是下面要说的深拷贝。

深拷贝

  1. 通过 JSON 的序列化和反序列化来实现:parse、stringify:
1
2
3
4
5
6
var obj = { name: "张三", dog: { name: "小黑" } };
var newObj = JSON.parse(JSON.stringify(obj));
console.log(obj, newObj); //{name:"张三",dog:{name:"小黑"}} {name:"张三",dog:{name:"小黑"}};
console.log(obj == newObj); //false
newObj.dog.name = "小花";
console.log(obj, newObj); //{name:"张三",dog:{name:"小黑"}} {name:"张三",dog:{name:"小花"}};

上面这种方式能满足我们日常业务需要,但是他有一个缺点,就是会忽略 undefined、任意的函数、symbol 值,因为 JSON 不支持这些数据类型;

  1. 递归调用
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/**
* 是否是"纯粹的对象":
* 该对象是通过 "{}" 或 "new Object" 创建的,该对象含有零个或者多个键值对
*/
var class2type = {};
var toString = class2type.toString;
var hasOwn = class2type.hasOwnProperty;

function isPlainObject(obj) {
var proto, Ctor;
if (!obj || toString.call(obj) !== "[object Object]") {
return false;
}
proto = Object.getPrototypeOf(obj);
if (!proto) {
return true;
}
Ctor = hasOwn.call(proto, "constructor") && proto.constructor;
return (
typeof Ctor === "function" &&
hasOwn.toString.call(Ctor) === hasOwn.toString.call(Object)
);
}
/**
* extend
* 内部方法:用户合并一个或多个对象到第一个对象
* @param {object} target 目标对象 对象都合并到target里
* @param {source} source 合并对象
* @param {boolean} deep 是否执行深度合并
*/
function extend(target, source, deep) {
for (var key in source) {
if (deep && (isPlainObject(source[key]) || Array.isArray(source[key]))) {
// source[key] 是对象,而 target[key] 不是对象, 则 target[key] = {} 初始化一下,否则递归会出错的
if (isPlainObject(source[key]) && !isPlainObject(target[key]))
target[key] = {};

// source[key] 是数组,而 target[key] 不是数组,则 target[key] = [] 初始化一下,否则递归会出错的
if (Array.isArray(source[key]) && !Array.isArray(target[key]))
target[key] = [];
// 执行递归
extend(target[key], source[key], deep);
}
// 不满足以上条件,说明 source[key] 是一般的值类型,直接赋值给 target 就是了
else if (source[key] !== undefined) target[key] = source[key];
}
}
var deepClone = function (target) {
var deep,
args = Array.prototype.slice.call(arguments, 1);

//第一个参数为boolean值时,表示是否深度合并
if (typeof target == "boolean") {
deep = target;
//target取第二个参数
target = args.shift();
}
// 遍历后面的参数,都合并到target上
args.forEach(function (arg) {
extend(target, arg, deep);
});
return target;
};
var obj = {name:123,age:123,c:{d:3},e:{f:function(){}}};
var obj2 = deepClone(true,{}, obj);
obj2.c.d = 12312;
console.log(obj);
console.log(obj2);