变量拷贝
Copy Composite Data Types: 复合类型的拷贝
Shallow Copy: 浅拷贝
顶层属性遍历
浅拷贝是指复制对象的时候,指对第一层键值对进行独立的复制。一个简单的实现如下:
// 浅拷贝实现
function shadowCopy(target, source) {
if (!source || typeof source !== "object") {
return;
} // 这个方法有点小trick,target一定得事先定义好,不然就不能改变实参了。// 具体原因解释可以看参考资料中 JS是值传递还是引用传递
if (!target || typeof target !== "object") {
return;
} // 这边最好区别一下对象和数组的复制
for (const key in source) {
if (source.hasOwnProperty(key)) {
target[key] = source[key];
}
}
}
//测试例子
const arr = [1, 2, 3];
const arr2 = [];
shadowCopy(arr2, arr);
console.log(arr2);
//[1,2,3]
const today = {
weather: "Sunny",
date: {
week: "Wed",
},
};
const tomorrow = {};
shadowCopy(tomorrow, today);
console.log(tomorrow);
// Object {weather: "Sunny", date: Object}
Object.assign
不过需要注意的是,assign 是浅拷贝,或者说,它是一级深拷贝,举两个例子说明:
const defaultOpt = {
title: {
text: 'hello world',
subtext: 'It\'s my world.'
}
};
const opt = Object.assign({}, defaultOpt, {
title: {
subtext: 'Yes, your world.'
}
});
console.log(opt);
// 预期结果
{
title: {
text: 'hello world',
subtext: 'Yes, your world.'
}
}
// 实际结果
{
title: {
subtext: 'Yes, your world.'
}
}
上面这个例子中,对于对象的一级子元素而言,只会替换引用,而不会动态的添加内容。那么,其实 assign 并没有解决对象的引用混乱问题,参考下下面这个例子:
const defaultOpt = {
title: {
text: 'hello world',
subtext: 'It\'s my world.'
}
};
const opt1 = Object.assign({}, defaultOpt);
const opt2 = Object.assign({}, defaultOpt);
opt2.title.subtext = 'Yes, your world.';
console.log('opt1:');
console.log(opt1);
console.log('opt2:');
console.log(opt2);
// 结果
opt1:
{
title: {
text: 'hello world',
subtext: 'Yes, your world.'
}
}
opt2:
{
title: {
text: 'hello world',
subtext: 'Yes, your world.'
}
}
使用 [].concat
来复制数组
同样类似于对于对象的复制,我们建议使用[].concat
来进行数组的深复制
const list = [1, 2, 3];
const changedList = [].concat(list);
changedList[1] = 2;
list === changedList; // false
同样的,concat
方法也只能保证一层深复制
> list = [[1,2,3]]
[ [ 1, 2, 3 ] ]
> new_list = [].concat(list)
[ [ 1, 2, 3 ] ]
> new_list[0][0] = 4
4
> list
[ [ 4, 2, 3 ] ]
浅拷贝的缺陷
不过需要注意的是,assign 是浅拷贝,或者说,它是一级深拷贝,举两个例子说明:
const defaultOpt = {
title: {
text: 'hello world',
subtext: 'It\'s my world.'
}
};
const opt = Object.assign({}, defaultOpt, {
title: {
subtext: 'Yes, your world.'
}
});
console.log(opt);
// 预期结果
{
title: {
text: 'hello world',
subtext: 'Yes, your world.'
}
}
// 实际结果
{
title: {
subtext: 'Yes, your world.'
}
}
上面这个例子中,对于对象的一级子元素而言,只会替换引用,而不会动态的添加内容。那么,其实 assign 并没有解决对象的引用混乱问题,参考下下面这个例子:
const defaultOpt = {
title: {
text: "hello world",
subtext: "It's my world.",
},
};
const opt1 = Object.assign({}, defaultOpt);
const opt2 = Object.assign({}, defaultOpt);
opt2.title.subtext = "Yes, your world.";
console.log("opt1:");
console.log(opt1);
console.log("opt2:");
console.log(opt2);
// 结果
// opt1:
// {
// title: {
// text: 'hello world',
// subtext: 'Yes, your world.'
// }
// }
// opt2:
// {
// title: {
// text: 'hello world',
// subtext: 'Yes, your world.'
// }
// }
DeepCopy: 深拷贝
递归属性遍历
一般来说,在 JavaScript 中考虑复合类型的深层复制的时候,往往就是指对于 Date、Object 与 Array 这三个复合类型的处理。我们能想到的最常用的方法就是先创建一个空的新对象,然后递归遍历旧对象,直到发现基础类型的子节点才赋予到新对象对应的位置。不过这种方法会存在一个问题,就是 JavaScript 中存在着神奇的原型机制,并且这个原型会在遍历的时候出现,然后原型不应该被赋予给新对象。那么在遍历的过程中,我们应该考虑使用hasOenProperty
方法来过滤掉那些继承自原型链上的属性:
function clone(obj) {
const copy; // Handle the 3 simple types, and null or undefined
if (null == obj || 'object' != typeof obj) return obj; // Handle Date
if (obj instanceof Date) {
copy = new Date();
copy.setTime(obj.getTime());
return copy;
} // Handle Array
if (obj instanceof Array) {
copy = [];
for (const i = 0, len = obj.length; i < len; i++) {
copy[i] = clone(obj[i]);
}
return copy;
} // Handle Object
if (obj instanceof Object) {
copy = {};
for (const attr in obj) {
if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]);
}
return copy;
}
throw new Error("Unable to copy obj! Its type isn't supported.");
}
调用如下:
// This would be cloneable:
const tree = {
left: { left: null, right: null, data: 3 },
right: null,
data: 8,
};
// This would kind-of work, but you would get 2 copies of the
// inner node instead of 2 references to the same copy
const directedAcylicGraph = {
left: { left: null, right: null, data: 3 },
data: 8,
};
directedAcyclicGraph["right"] = directedAcyclicGraph["left"];
// Cloning this would cause a stack overflow due to infinite recursion:
const cylicGraph = {
left: { left: null, right: null, data: 3 },
data: 8,
};
cylicGraph["right"] = cylicGraph;
利用 JSON 深拷贝
JSON.parse(JSON.stringify(obj));
对于一般的需求是可以满足的,但是它有缺点。下例中,可以看到 JSON 复制会忽略掉值为 undefined 以及函数表达式。
const obj = {
a: 1,
b: 2,
c: undefined,
sum: function () {
return a + b;
},
};
const obj2 = JSON.parse(JSON.stringify(obj));
console.log(obj2);
//Object {a: 1, b: 2}