在讲深拷贝和浅拷贝之前要先了解一下不同数据类型的存储方式
数据的存储方式
数据分为基本数据类型(String, Number, Boolean, Null, Undefined,Symbol)和引用数据类型(Object, Array)
基本数据类型存储:数据直接存储在栈中
引用数据类型的存储:真实的数据存放在堆内存里,在栈中存储的是对象的指针
浅拷贝与深拷贝定义区别
浅拷贝与深拷贝是针对引用类型值的拷贝
浅拷贝:仅仅复制对象的引用,而不是对象本身,新旧对象还是共享同一块内存,
深拷贝:将对象完整地拷贝出来,开辟新的内存来存放新对象,新对象跟原对象完全独立,不共享内存
区别:浅拷贝只复制对象的第一层属性、深拷贝可以对对象的属性进行递归复制,深拷贝相比于浅拷贝速度较慢并且花销较大。
浅拷贝的实现
1.“=” 赋值
最基础的赋值方法,只是将对象的引用赋值。
2. Object.assign 拷贝的是对象的属性的引用,而不是对象本身
它不会拷贝对象的继承属性
它不会拷贝对象的不可枚举属性
let target = {};
let source = { a: { b: 2 }, c: 2, sym: Symbol(1) };
Object.defineProperty(source, 'innumerable' ,{
value: '不可枚举属性',
enumerable: false
});
Object.assign(target, source);
console.log(target); // { a: { b: 10 }, c: 2, sym: Symbol(1) }
source.a.b = 10;
source.c = 10;
console.log(source); // { a: { b: 10 }, c: 10, sym: Symbol(1), innumerable: '不可枚举属性' }
console.log(target); // { a: { b: 10 }, c: 2, sym: Symbol(1) }
- Object.create(obj1) 只拷贝一层属性
- 扩展运算符
let source = { a: { b: 2 }, c: 2, sym: Symbol(1) };
Object.defineProperty(source, 'innumerable' ,{
value: '不可枚举属性',
enumerable: false
});
let target = {...source};
console.log(target); // { a: { b: 10 }, c: 2, sym: Symbol(1) }
source.a.b = 10;
source.c = 10;
console.log(source); // { a: { b: 10 }, c: 10, sym: Symbol(1), innumerable: '不可枚举属性' }
console.log(target); // { a: { b: 10 }, c: 2, sym: Symbol(1) }
- slice和concat
slice也只能是处理一层的深度拷贝。如果存在对象的嵌套,它也无能为力
let arr = [1, 2, {val: 4}];
let newArr = arr.slice();
newArr[2].val = 1000;
console.log(arr); // [ 1, 2, { val: 1000 } ]
concat
let arr = [1, 2, 3];
let newArr = arr.concat();
newArr[1] = 100;
console.log(arr); // [ 1, 2, 3 ]
console.log(newArr); // [ 1, 100, 3 ]
- 手动浅拷贝(只拷贝第一层属性)
const shallowCopy = target => {
if (typeof target === 'object' && target !== null) {
const cloneTarget = Array.isArray(target) ? [] : {};
for (let key in target) {
if (target.hasOwnProperty(key)) {
cloneTarget[key] = target[key];
}
}
return cloneTarget;
} else {
return target;
}
};
深拷贝的实现
- 使用JSON.stringify和JSON.parse实现深拷贝
var obj1 = { body: { a: 10 } };
var obj2 = JSON.parse(JSON.stringify(obj1));
obj2.body.a = 20;
console.log(obj1); // { body: { a: 10 } } <-- 沒被改到
console.log(obj2); // { body: { a: 20 } }
console.log(obj1 === obj2); // false
console.log(obj1.body === obj2.body); // false
缺陷:
1. 它会抛弃对象的constructor,深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成Object;
2. 只能处理的对象只有 Number, String, Boolean, Array,扁平对象,无法转换函数、undefined、symbol数据类型拷贝
3. 对象中的值为Date时,会变成字符串。
4. 无法拷贝不可枚举的属性。
5. 拷贝的对象中的值为RegExp时,会变成空对象。
6. 拷贝的对象中的值为NaN、Infinity或者-Infinity时,会变成null
7. 无法拷贝对象的循环应用,即对象成环(obj[key] = obj);
- 递归深拷贝
function deepCopy(obj1) {
// 创建一个空的对象或数组
var obj2 = Array.isArray(obj1) ? [] : {};
if (obj1 && typeof obj1 === "object") {
for (var i in obj1) {
let prop = obj1[i];
// 避免相互引用造成死循环,如obj1.a=obj
if (prop == obj1) {
continue;
}
if (obj1.hasOwnProperty(i)) {
// 如果子属性为引用数据类型,递归复制
if (prop && typeof prop === "object") {
obj2[i] = deepCopy(prop);
} else {
// 如果是基本数据类型,只是简单的复制
obj2[i] = prop;
}
}
}
}
return obj2;
}
缺陷:
当遇到两个互相引用的对象,会出现死循环的情况,
这个深拷贝方法不可拷贝不可枚举的属性
这个方法对于Date、RegExp不能够正确的拷贝
无法拷贝对象的循环应用,即对象成环(obj[key] = obj);
JSON.stringify和递归深拷贝结果对比
function Obj() {
this.func = function() { alert(1); };
this.obj = { a: 1 };
this.arr = [1, 2, 3];
this.reg = /123/;
this.und = undefined;
this.date = new Date(0);
this.NaN = NaN;
this.Infinity = Infinity;
this.sym = Symbol(1);
}
let obj1 = new Obj();
Object.defineProperty(obj1,'innumerable',{
enumerable: false,
value: 'innumerable'
});
console.log('obj1', obj1);
let str = JSON.stringify(obj1);
let obj2 = JSON.parse(str);
let obj3 = deepClone(obj1);
console.log('obj2', obj2);
console.log('obj3', obj3);
- 改进版深度拷贝
要更好的实现一个深拷贝的方法,那么必须要了解以下四点:
对于不可枚举的属性,我们采用Reflect.ownKeys方法。
当参数为Date、RegExp类型时,则直接生成一个新的实例返回。
利用Object的getOwnPropertyDescriptors方法可以获得对象的所有属性,以及对应的特性。
顺便结合Object.create创建对象,并继承传入原对象的原型链。
利用WeakMap类型作为哈希表,作为检测循环引用。如果存在循环,则引用直接返回WeakMap存储的值。
const isComplexDataType = obj => (typeof obj === "object" || typeof obj === "function") && obj !== null;
const deepClone = function(origin, hash = new WeakMap()) {
if (origin.constructor === Date) return new Date(origin);
if (origin.constructor === RegExp) return new RegExp(origin);
// 如果循环引用就用WeakMap解决
if (hash.has(origin)) return hash.get(origin);
// 遍历传入参数所有键的特性
let allDesc = Object.getOwnPropertyDescriptors(origin);
// 继承原型链
let cloneObj = Object.create(Object.getPrototypeOf(origin), allDesc);
hash.set(origin, cloneObj);
for (let key of Reflect.ownKeys(origin)) {
cloneObj[key] = (isComplexDataType(origin[key]) && typeof origin[key] !== "function" ? deepClone(origin[key], hash) : origin[key];
}
return cloneObj;
}
- jquery 提供一个$.extend可以用来做深拷贝
var obj2 = $.extend(true, {}, obj1);