JavaScript 之深浅拷贝™

1. JavaScript 数据类型

Javascript 有六种基本数据类型(也就是简单数据类型),它们分别是:Undefined,Null,Boolean,Symbol,Number 和 String。还含有一种复杂数据类型,就是 Object(对象)。

注意 Undefined 和 Null 的区别,Undefined 类型只有一个值,就是 undefined,Null 类型也只有一个值,也就是 null

  • undefined 实就是已声明未赋值的变量输出的结果
  • null 空对象的引用
1
2
3
var a;
console.log(a); // undefined
console.log(document.getElementById('a')); // null

2. 数据类型的区别

2.1. 简单数据类型

它们值在占据了内存中固定大小的空间,并被保存在栈内存中。当一个变量向另一个变量复制基本类型的值,会创建这个值的一个副本,还有就是不能给基本数据类型的值添加属性。

1
2
3
4
var a = 1;
var b = a;
a.attr = 'hello';
console.log(a.attr); // undefined

上面代码中 a 就是简单数据类型(Number),b 就是 a 的副本,它们两者都占有不同位置但相等的内存空间。

2.2. 复杂的数据类型

复杂的数据类型即引用类型,它的值是对象,保存在堆内存中,包含引用类型值的变量实际上包含的并不是对象本身,而是一个指向该对象的指针。从一个变量向另一个变量复制引用类型的值,复制的其实是指针,因此两个变量最终都指向同一个对象。

3. 浅拷贝

下面这段代码就是浅拷贝,有时候我们只是想备份数组,但是只是简单让它赋给一个变量,改变其中一个,另外一个就紧跟着改变,但很多时候这不是我们想要的。

1
2
3
4
5
6
7
8
var obj = {
name: 'xiguapi',
age: 23
}
var obj2 = obj;
obj2['age'] = 22;
console.log(obj);// { name: 'xiguapi', age: 22 }
console.log(obj2);// { name: 'xiguapi', age: 22 }

我们可以看到 obj 赋值给 obj2 后,当我们更改其中一个对象的属性值,两个对象都发生了改变,究其原因局势因为 obj 和 obj2 这两个变量都指向同一个指针,赋值只是复制了指针,所以当我们改变其中一个的值就会影响另外一个变量的值。

ES6 中的 Object.assign() 只是一级属性复制,比浅拷贝多深拷贝了一层而已。不能使用 Object.assign() 进行深拷贝。

4. 深拷贝

4.1. 基本类型数组

对于基本数据类型数组我们可以使用 slice()concat() 方法进行深拷贝。

4.1.1. slice

1
2
3
4
5
var arr = ['xiguapi', 22];
var arrCopy = arr.slice();
arrCopy[1] = 23;
console.log(arr); // [ 'xiguapi', 22 ]
console.log(arrCopy); // [ 'xiguapi', 23 ]

4.1.2. concat

1
2
3
4
5
var arr = ['xiguapi', 22];
var arrCopy = arr.concat();
arrCopy[0] = 'xiezixi';
console.log(arr); // [ 'xiguapi', 22 ]
console.log(arrCopy); // [ 'xiezixi', 22 ]

4.2. 对象

4.2.1. 封装方法

对象我们可以定义一个封装好的方法来处理对象的深拷贝。

1
2
3
4
5
6
7
8
9
10
11
var deepCopy = function (source) {
var result = {};
for (var key in source) {
if (typeof source[key] === 'object') {
result[key] = deepCopy(source[key])
} else {
result[key] = source[key]
}
}
return result;
}

1
2
3
4
5
6
7
8
var obj = {
name: 'xiguapi',
age: 23
}
var obj1 = deepCopy(obj);
obj1['age'] = 22;
console.log(obj); // { name: 'xiguapi', age: 23 }
console.log(obj1); // { name: 'xiguapi', age: 22 }

4.2.2. JSON

如果数据中没有函数,可以直接用JSON来实现拷贝,如果有函数,就得用递归来实现深拷贝。需要兼容不同的浏览器并考虑速度的话,推荐使用 JSON.parse(JSON.stringify())

1
2
3
4
5
6
7
8
var obj = {
name: 'xiguapi',
age: 23
}
var obj1 = JSON.parse(JSON.stringify(obj));
obj1['age'] = 22;
console.log(obj); // { name: 'xiguapi', age: 23 }
console.log(obj1); // { name: 'xiguapi', age: 22 }

4.2.3. jQuery $.clone()

在 jQuery 中也有这么一个叫 $.clone() 的方法,可是它并不是用于一般的 JS 对象的深拷贝,而是用于 DOM 对象。

4.2.4. jQuery $.extend()

可以通过 $.extend() 方法来完成深拷贝。值得庆幸的是,我们在 jQuery 中可以通过添加一个参数来实现递归 extend。调用 $.extend(true, {}, ...) 就可以实现深拷贝。

1
2
3
4
5
6
7
8
var obj = {
name: 'xiguapi',
age: 23
}
var obj1 = $.extend(true, {}, obj);
obj1['age'] = 22;
console.log(obj); // { name: 'xiguapi', age: 23 }
console.log(obj1); // { name: 'xiguapi', age: 22 }