更详细的文章请转看: 浅谈Javacript浅拷贝和深拷贝的实现
深拷贝和浅拷贝,主要是对象发生复制的时候,根据复制的层级不同来区分的。很多人在这里经常变量赋值发生混淆。我们前面知道了,对于JavaScript数组等复杂的数据类型来说,将其赋值给其它变量,其实只是复制了对象的地址给它,两个变量指向的是同一个对象,因此普通的赋值既不是深拷贝也不是浅拷贝。
浅拷贝
所谓浅拷贝就是指对象复制的时候只复制一层。
var xiaoHong = {
name: '小红',
age: 18
}
var zhangSan = {
name: '张三',
age: 20,
girl: xiaoHong
}
上面的代码在内存中我们可以这样描述:
也就是说,对象里面的值还是个对象(引用类型),如果我们去复制zhangSan
这个对象,我们可以这样复制:
var xiaoMing = {};
// 将zhangSan的所有属性都复制给xiaoMing
for (var i in zhangSan){
// 检查对象是否有用某个属性
if (zhangSan.hasOwnProperty(i)) {
xiaoMing[i] = zhangSan[i];
}
}
console.log(xiaoMing);
我们在继续运行代码:
xiaoMing.girl.name = '花花';
// 我们发现,这两个对象的girl属性指向的是同一个对象
console.log(xiaoMing);
console.log(zhangSan);
我们发现,这两个对象的girl属性指向的事同一个对象,一个发生变化的时候,另一个跟着也会变。这种只复制对象一层的情况,我们称之为浅拷贝,内存中的样子是这样的:
// 浅拷贝封装的函数
// 对象的拷贝的目的是得到一个新对象
function simpleCopy(obj) {
var newObj = {};
for (var key in obj) {
// 检查对象是否有用某个属性
if (obj.hasOwnProperty(key)) {
newObj[key] = obj[key];
}
}
return newObj;
}
深拷贝
深拷贝是指复制对象的所有层级。就像前面的例子一样,深拷贝完成后应该是这样的效果:
也就是说,所有层级都是复制出来了,而且互不干涉,就像克隆的一样,长得一模一样,但是所有内容都是相互独立。
怎么实现深拷贝的效果呢?常用的方法有两种:
-
转成JSON字符串,再转成对象。
var xiaoHong = { name: '小红', age: 18 } var zhangSan = { name: '张三', age: 20, girl: xiaoHong } var xiaoMing = JSON.parse(JSON.stringify(zhangSan)); xiaoMing.girl.name = '小花'; console.log(xiaoMing); console.log(zhangSan);
-
使用递归处理
我们上面的例子写的比较简单,只有两层,因此深拷贝也只需要拷贝两层,其实我们很多时候是不知道一个对象有多少层级的,因此递归比较适合。
var xiaoHong = { name: '小红', age: 18 } var zhangSan = { name: '张三', age: 20, girl: xiaoHong } var xiaoMing = deepCopy(zhangSan); xiaoMing.girl.name = '小花'; console.log(xiaoMing); console.log(zhangSan);
实现深拷贝:
// 方法一:
function deepClone(obj) {
var result = Array.isArray(obj) ? [] : {};
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
if (typeof obj[key] === 'object' && obj[key]!==null) {
result[key] = deepClone(obj[key]);
} else {
result[key] = obj[key];
}
}
}
return result;
}
方法二:
function deepClone(arr){
return JSON.parse(JSON.stringify(arr))
}
递归函数
所谓递归函数就是指调用自己的函数。
// 求两个整数之间的所有整数的和
function sum(start, end) {
var total = start;
if (start < end) {
start++;
// 自己调用自己了
total = total + sum(start, end);
}
return total;
}
console.log(sum(1, 5));
/**
* total 的值的变化
* start = 1 时
* total = 1 + sum(2,5) 在内存中等着
* start = 2 时
* total = 2 + sum(3,5) 在内存中等着
* start = 3 时
* total = 3 + sum(4,5) 在内存中等着
* start = 4 时
* total = 4 + sum(5,5) 在内存中等着
* start = 5 时,发现不符合继续调用条件,if执行完,继续执行下面的代码
* 由于if之前发生了一次total的赋值,此时return的total的值为5;即sum(5,5)的之是5
* 然后在内存中等着的total = 4 + sum(5,5) 的值就是 4 + 5 = 9,执行完后继续执行到return total,
* 返回值是 9,即:sum(4,5) 返回9
* 然后在内存中等着的total = 3 + sum(4,5) 的值就是 3 + 9 = 12,执行完后继续执行到return total,
* 返回值是 12,即:sum(3,5) 返回12
* 然后在内存中等着的total = 2 + sum(3,5) 的值就是 2 + 12 = 14,执行完后继续执行到return total,
* 返回值是 14,即:sum(2,5) 返回14
* 然后在内存中等着的total = 1 + sum(2,5) 的值就是 1 + 14 = 15
* 此时相当于第一轮的if语句执行完,继续执行到return total,最终返回值为15
*/