引用类型
之所以会出现深浅拷贝的问题,实质上是由于JS对基本类型和引用类型的处理不同。基本类型指的是简单的数据段,而引用类型指的是一个对象,而JS不允许我们直接操作内存中的地址,也就是不能操作对象的内存空间,所以,我们对对象的操作都只是在操作它的引用而已。
在复制时也是一样,如果我们复制一个基本类型的值时,会创建一个新值,并把它保存在新的变量的位置上。而如果我们复制一个引用类型时,同样会把变量中的值复制一份放到新的变量空间里,但此时复制的东西并不是对象本身,而是指向该对象的指针。所以我们复制引用类型后,两个变量其实指向同一个对象,改变其中一个对象,会影响到另外一个。
var num = 10;
var obj = {
name: 'Nicholas'
}
var num2 = num;
var obj2 = obj;
obj.name = 'Lee';
obj2.name; // 'Lee'
浅拷贝
1.Object.assign()
其中第一个参数是我们最终复制的目标对象,后面的所有参数是我们的即将复制的源对象,支持对象或数组,一般调用的方式为
var newObj = Object.assign({}, originObj);
这样我们就得到了一个新的浅拷贝对象。另外[].slice()方法可以视为数组对象的浅拷贝。
2. 自定义浅拷贝
如果我们要复制对象的所有属性都不是引用类型时,就可以使用浅拷贝,实现方式就是遍历并复制,最后返回新的对象。
function shallowCopy(obj) {
var copy = {};
// 只复制可遍历的属性
for (key in obj) {
// 只复制本身拥有的属性
if (obj.hasOwnProperty(key)) {
copy[key] = obj[key];
}
}
return copy;
}
如上面所说,我们使用浅拷贝会复制所有引用对象的指针,而不是具体的值,所以使用时一定要明确自己的需求,同时,浅拷贝的实现也是最简单的。
深拷贝
如果我们需要复制一个拥有所有属性和方法的新对象,就要用到深拷贝,JS并没有内置深拷贝方法,主要是因为:
- 深拷贝怎么定义?我们怎么处理原型?怎么区分可拷贝的对象?原生DOM/BOM对象怎么拷贝?函数是新建还是引用?这些edge case太多导致我们无法统一概念,造出大家都满意的深拷贝方法来。
- 内部循环引用怎么处理,是不是保存每个遍历过的对象列表,每次进行对比,然后再造一个循环引用来?这样带来的性能消耗可以接受吗。
var obj1 = {
age: 20,
name: 'xxx',
address: {
city: 'beijing'
},
arr: ['a', 'b', 'c']
}
var obj2 = deepClone(obj1)
obj2.address.city = '上海'
console.log(obj2)
console.log(obj1)
function deepClone(obj = {}) {
if (typeof obj !== 'object' || obj == null) {
return obj
}
let result;
if (obj instanceof Array) {
result = []
} else {
result = {}
}
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
result[key] = deepClone(obj[key]);
}
}
return result
}