浅拷贝
浅拷贝就是复制一个新的值或者一个引用。如果复制的的对象属性是基础数据类型,则就是复制一个新的基础类型值,如果属性是引用类型,则复制引用类型的内存地址。
浅拷贝方法
Object.assign
使用Object.assign方法,assign用于合并多个对象。使用assign方法将对象合并发哦一个空对象上,从而可以达到复制对象。
Object.assign用法: Object.assign(target, ...source);
let target = {};
let source = {
a: 1,
b: [100],
c: Symbol(1)
};
Object.assign(target, source);
console.log(target); // {a: 1, b: [100], c: Symbol(1)}
使用Object.assign需要注意:
- Object.assign不会拷贝不可枚举属性
- 不会拷贝对象继承的属性
- 可以拷贝Symbol属性
扩展运算符
可以使用...扩展运算符来实现浅拷贝。
let obj = {a:1};
let cloneObj = {...a};
console.log(cloneObj); // {a:1}
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 ]
slice拷贝数组
slice方法也只能拷贝数组类型,slice方法会返回一个新的数组对象。
slice用法:arr.slice(begin, end);
let arr = [1, 2, {val: 4}];
let newArr = arr.slice();
newArr[2].val = 1000;
console.log(arr); //[ 1, 2, { val: 1000 } ]
手动实现一个浅拷贝方法
const shallowClone = (target: any[] | {}): any[] | {} => {
if (typeof target === 'object' && target !== null) {
const cloneTarget = Array.isArray(target) ? [] : {};
for (const key in target) {
cloneTarget[key] = target[key];
}
return cloneTarget;
}
return target;
}
深拷贝
浅拷贝只是创建了一个对象,复制了原有对象的基本类型的值,而引用数据类型只拷贝了一层属性,再深层的拷贝是无法进行的。而深拷贝则不同,对于复杂的引用类型,其在堆内存中完全开辟了一个新内存地址,并将原有的对象完全复制过来存放。可以这样子总结:
将一个对象从内存中完整地拷贝出来一份给目标对象,并从堆内存中开辟一个全新的空间存放新对象,且新对象的修改并不会改变原对象,二者实现真正的分离。
实现方法
JSON.stringfy
使用stringfy方法就是现将对象转换成序列化的字符串,然后再通过JSON.parse方法将字符串生成一个新的对象。
let obj1 = { a:1, b:[1,2,3] }
let str = JSON.stringify(obj1);
let obj2 = JSON.parse(str);
console.log(obj2); //{a:1,b:[1,2,3]}
obj1.a = 2;
obj1.b.push(4);
console.log(obj1); //{a:2,b:[1,2,3,4]}
console.log(obj2); //{a:1,b:[1,2,3]}
使用JSON.stringfy方法时需要注意:
- 拷贝的对象中如果有函数、undefined、symbol这几种类型,经过JSON.stringfy方法序列化后的字符串中这个键值对会消失
- 拷贝Date引用类型会变成字符串
- 无法拷贝不可枚举的属性
- 无法拷贝对象的原型链
- 拷贝RegExp引用类型会变成空对象
- 对象中含有NaN、Infinity以及-Infinity,JSON序列化的结果会变成null
- 无法拷贝对象的循环引用,即对象成环(obj[key] = obj)
function Obj() {
this.func = function () { alert(1) };
this.obj = {a:1};
this.arr = [1,2,3];
this.und = undefined;
this.reg = /123/;
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);
console.log('obj2',obj2);
基础版
let obj = {
c: {
a: 123
}
}
const deepClone = (target) => {
let cloneObj = {};
for (const key in target) {
if (typeof target[key] === 'object') {
cloneObj[key] = deepClone[target[key]]
} else {
cloneObj[key] = target[key]
}
}
return cloneObj;
}
利用递归能实现一个简单的深拷贝,但是存在一些问题:
- 这个深拷贝函数并不能复制不可枚举的属性以及Symbol类型的数据
- 这种方法只是针对普通的引用类型的值做递归复制,而对于Array、Date、RegExp、Error、Function这样的引用类型并不能正确地拷贝
- 对象属性成环,即循环引用没有解决。
改进版
针对简单深拷贝函数不能复制不可枚举属性和Symbol类型,可以使用Reflect.ownKeys()方法;Data跟RegExp类型则可以直接生成一个新的实例返回即可;利用Object的getOwenPropertyDescriptors获取对象的所有属性以及相应的特性,然后使用Object的create方法创建一个新对象,并集成传入原对象的原型链。如果存在循环引用,可以使用weakMap作为hash表,weakMap是弱引用类型,可以有效防止内存泄漏,如果存在循环,则引用直接范湖weakMap存储的值。
const isComplexDataType = obj => (typeof obj === 'object' || typeof obj === 'function') && (obj !== null)
const deepClone = function (obj, hash = new WeakMap()) {
if (obj.constructor === Date)
return new Date(obj) // 日期对象直接返回一个新的日期对象
if (obj.constructor === RegExp)
return new RegExp(obj) //正则对象直接返回一个新的正则对象
//如果循环引用了就用 weakMap 来解决
if (hash.has(obj)) return hash.get(obj)
let allDesc = Object.getOwnPropertyDescriptors(obj)
//遍历传入参数所有键的特性
let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc)
//继承原型链
hash.set(obj, cloneObj)
for (let key of Reflect.ownKeys(obj)) {
cloneObj[key] = (isComplexDataType(obj[key]) && typeof obj[key] !== 'function') ? deepClone(obj[key], hash) : obj[key]
}
**return** cloneObj
}
// 下面是验证代码
let obj = {
num: 0,
str: '',
boolean: true,
unf: undefined,
nul: null,
obj: { name: '我是一个对象', id: 1 },
arr: [0, 1, 2],
func: function () { console.log('我是一个函数') },
date: new Date(0),
reg: new RegExp('/我是一个正则/ig'),
[Symbol('1')]: 1,
};
Object.defineProperty(obj, 'innumerable', {
enumerable: false, value: '不可枚举属性' }
);
obj = Object.create(obj, Object.getOwnPropertyDescriptors(obj))
obj.loop = obj // 设置loop成循环引用的属性
let cloneObj = deepClone(obj)
cloneObj.arr.push(4)
console.log('obj', obj)
console.log('cloneObj', cloneObj)
注:文章里面的内容参考了网上的文章,由于文章在笔记里面待的时间太久了,已经不记得引用地址。。。