在 JavaScript 中有两中变量类型数据, 基本类型和引用类型. 对值类型的复制操作会对变量值进行拷贝, 两者互不相干. 而引用类型只会对存储变量的指针地址进行拷贝, 导致两个变量指向同一个地址, 也就是同一份数据, 修改其中一个会直接影响另外一个.
而我们要谈的浅拷贝与深拷贝就是专指引用类型.
浅拷贝
浅拷贝是最简单易理解的, 只对引用类型进行一级拷贝.
- 自行实现
function shallowClone(source) {
var target = {};
for(var i in source) {
if (source.hasOwnProperty(i)) {
target[i] = source[i];
}
}
return target;
}
- Object.assign
let obj = {
a: 1,
b: 2,
};
let objCopy = Object.assign({}, obj);
console.log(objCopy);
// Result - { a: 1, b: 2 }
上面的代码会将所有的可枚举的自身属性复制到目标对象.
- 扩展操作符 (...)
var sourceObject = {
a: 1,
b: function() {
return a;
}
};
var targetObject = { ...sourceObject };
console.log(targetObject.b === sourceObject.b); // true
浅拷贝只会对一级进行拷贝, 对于嵌套的引用类型依然是同一个指针地址.
深拷贝
深拷贝会对任意的嵌套层级进行拷贝, 保证所有的引用类型全部是新对象, 不会和原对象有任何的关系, 彼此的修改不会互相影响.
- 奇淫巧技 JSON.parse(JSON.stringify(object))
let obj = {
a: 1,
b: {
c: 2,
},
}
let newObj = JSON.parse(JSON.stringify(obj));
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 }
上面的方法虽然简单但是有很多的问题.
第一个: 不能拷贝函数
let obj = {
name: 'neo',
sayName: function exec() {
return this.name;
},
}
let method = JSON.parse(JSON.stringify(obj));
console.log(method); // JSON.parse(JSON.stringify(obj))
/*
{
name: "neo"
}
*/
第二个: 循环引用
循环引用是说对象的对象引用了他们自身. 这个是深拷贝中一个老生常谈的问题.
// 循环引用
let obj = {
a: 'a',
b: {
c: 'c',
d: 'd',
},
}
obj.c = obj.b;
obj.e = obj.a;
obj.b.c = obj.c;
obj.b.d = obj.b;
obj.b.e = obj.b.c;
let newObj = JSON.parse(JSON.stringify(obj));
console.log(newObj);
上面代码执行的结果是:
所以说, JSON.parse(JSON.stringify(obj)) 没办法解决循环引用的问题.
第三个: 值为 undefined 的属性会被忽略
var obj = {
a: 'lendel',
b: undefined
};
var newObj = JSON.parse(JSON.stringify(obj));
console.log(newObj);
// 结果
/*
{ a: 'lendel' }
*/
第四个: Infinity 值会被置为 null
var obj = {
a: 'lendel',
i: Infinity
};
var newObj = JSON.parse(JSON.stringify(obj));
console.log(newObj);
// 结果
/*
{ a: 'lendel', i: null }
*/
第五个: 一些对象会被转为字符串
这种方法会将一些类型的对象转变字符串, 例如 Date, Set, Map......
var obj = {
a: 'lendel',
d: new Date()
};
var newObj = JSON.parse(JSON.stringify(obj));
console.log(newObj);
// 结果
/*
{ a: 'lendel', d: '2019-04-01T02:53:37.720Z' }
*/
一个简单的深拷贝方法
function cloneObject(obj) {
var clone = {};
for(var i in obj) {
if(obj[i] != null && typeof(obj[i])=="object")
clone[i] = cloneObject(obj[i]);
else
clone[i] = obj[i];
}
return clone;
}
这是一段非常简单的深拷贝函数, 因为简单,所以问题也多. 参数的校验并不完备, 利用递归时当层次过深的时候容易造成栈溢出, 还有循环引用.
上面的代码体现了深拷贝的基本思想,但实际使用中建议使用 lodash 等现成的库
对于栈溢出的问题, 我们可以将递归改用循环来解决:
function loopClone(x) {
const root = {};
// 栈
const loopList = [
{
parent: root,
key: undefined,
data: x
}
];
while (loopList.length) {
// 深度优先
const node = loopList.pop();
const parent = node.parent;
const key = node.key;
const data = node.data;
// 初始化赋值目标,key为undefined则拷贝到父元素,否则拷贝到子元素
let res = parent;
if (typeof key !== 'undefined') {
res = parent[key] = {};
}
for (let k in data) {
if (data.hasOwnProperty(k)) {
if (typeof data[k] === 'object') {
// 下一次循环
loopList.push({
parent: res,
key: k,
data: data[k]
});
} else {
res[k] = data[k];
}
}
}
}
return roo
代码来源: https:// yanhaijing.com/javascri pt/2018/10/10/clone-deep/
结语:
写的时候我一直在想一个问题, 我究竟需不需要自己写一个深拷贝函数或类似的. 对于学习来说这是件无关紧要的事, 但是想要在产品中使用自己写的其实是挺蠢的事. 并不是说我们实现不出来, 而是投入时间的性价比实在太低. 要完成一个工业级别的函数需要大量的测试, 要覆盖到每一种情况,这是件得不偿失的事. 并且是在网上有那么多经过上千场景验证过的现成框架的情况下.所以, 要在合适的情况下选择合适的工具.
文章参考: https:// flaviocopes.com/how-to- clone-javascript-object/ https:// yanhaijing.com/javascri pt/2018/10/10/clone-deep/