浅拷贝:只复制指向某个对象的指针,而不是复制对象本身,新旧对象还是共享同一块内存。
这不是真正的拷贝,你不是真正的自我
深拷贝:另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会影响原对象。
突然的自我
接下描述三种拷贝方式,嗯。。。
拷贝方式
一、JSON.parse(JSON.stringify(obj))
非常的简单,深度拷贝,就这一行,当然戳手可得的东西总是藏着很多隐患。列举一二┗( ▔, ▔ )┛
- undefined、任意的函数以及 symbol 三个特殊的值无效
- undefined、任意的函数以及 symbol 作为对象属性值时 JSON.stringify() 对跳过它们进行序列化,没了就是没了
- undefined、任意的函数以及 symbol 作为数组元素值时,JSON.stringify() 会将它们序列化为 null
- undefined、任意的函数以及 symbol 被 JSON.stringify() 作为单独的值进行序列化时,都会返回 undefined
- 所有以 symbol 为属性键的属性都会被完全忽略掉
- 会抛弃对象的constructor,所有的构造函数会指向Object
- 对象循环引用会报错
- 不可枚举属性是不能拷贝的
二、Object.assign(target, …sources)
用于将所有可枚举属性的值从一个或多个sources复制到target(相当于两者求并集)。它将返回目标对象。
针对深拷贝,需要使用其他办法,因为 Object.assign()拷贝的是属性值。假如源对象的属性值是一个对象的引用,那么它也只指向那个引用。
比第一个好的地方在于可以拷贝三个特殊值,不会出错。
当然,不可枚举属性也是不能拷贝的。
三、递归调用(考虑循环引用)
WeakMap的设计目的在于,有时我们想在某个对象上面存放一些数据,Map会形成对于这个对象的引用。如果不删除这个引用,垃圾回收机制就不是释放。而WeakMap是弱引用,不计数。
function isObj(obj) {
return typeof obj === 'object' && obj !== null
}
/**
* 深度拷贝
* @param obj 需要拷贝的对象
* @param hash 此参数不需要传参数
*/
function clone (obj:any, hash = new WeakMap()):any {
if(hash.has(obj)) return hash.get(obj)
let cloneObj = Array.isArray(obj) ? [] : {}
hash.set(obj, cloneObj)
for (let key in obj) {
cloneObj[key] = isObj(obj[key]) ? clone(obj[key], hash) : obj[key];
}
return cloneObj;
}
没解决的问题
当然啦,第三种方法还有不够全面,有些对象不能正确复制。
特殊对象:
Bool、Number、String、String、Date、Error、Map、Set这几种类型,我就称为特殊对象。
function cloneSymbol(targe) {
return Object(Symbol.prototype.valueOf.call(targe));
}
function cloneReg(targe) {
const reFlags = /\w*$/;
const result = new targe.constructor(targe.source, reFlags.exec(targe));
result.lastIndex = targe.lastIndex;
return result;
}
function cloneOtherType(target) {
const Ctor = target.constructor;
switch (Ctor) {
case Boolean:
case Number:
case String:
case Error:
case Date:
return new Ctor(target.valueOf());
case RegExp:
return cloneReg(target);
case Symbol:
return cloneSymbol(target);
case Map:
case WeakMap:
case Set:
case WeakSet:
return new Ctor([...target]);
default:
return null;
}
}
函数:
普通函数直接浅复制就可以了,但是箭头函数浅复制不行。因为 this在函数执行时确认,不在定义时(this永远指向最后调用它的那个对象),但是箭头函数的this在定义时确定。 会导致什么问题呢?
那就是箭头函数如果是浅拷贝,它运行时this还指向拷贝的对象,而不是拷贝后的新对象,是不是很可怕?|墙|ョ゚ェ゚;)
//判断是否为箭头函数
function isArrowFunction(obj) {
return typeof obj === 'function' && obj.prototype === undefined;
}
//拷贝箭头函数
function copyFunction(func) {
const bodyReg = /(?<={)(.|\n)+(?=})/m;
const paramReg = /(?<=\().+(?=\)\s+{)/;
const funcString = func.toString();
if (func.prototype) {
console.log('普通函数');
const param = paramReg.exec(funcString);
const body = bodyReg.exec(funcString);
if (body) {
console.log('匹配到函数体:', body[0]);
if (param) {
const paramArr = param[0].split(',');
console.log('匹配到参数:', paramArr);
return new Function(...paramArr, body[0]);
} else {
return new Function(body[0]);
}
} else {
return null;
}
} else {
return eval(funcString);
}
}
不可枚举属性和属性名为Symbol的 :
Object.getOwnPropertyNames() 方法返回一个由指定对象的所有自身属性的属性名(包括不可枚举属性但不包括Symbol值作为名称的属性)组成的数组。
Object.getOwnPropertySymbols() 方法返回一个给定对象自身的所有 Symbol 属性的数组。
最终版本
function isObj(obj) {
return typeof obj === 'object' && obj !== null;
}
function isArrowFunction(obj) {
return typeof obj === 'function' && obj.prototype === undefined;
}
function copyFunction(func) {
const bodyReg = /(?<={)(.|\n)+(?=})/m;
const paramReg = /(?<=\().+(?=\)\s+{)/;
const funcString = func.toString();
if (func.prototype) {
console.log('普通函数');
const param = paramReg.exec(funcString);
const body = bodyReg.exec(funcString);
if (body) {
console.log('匹配到函数体:', body[0]);
if (param) {
const paramArr = param[0].split(',');
console.log('匹配到参数:', paramArr);
return new Function(...paramArr, body[0]);
} else {
return new Function(body[0]);
}
} else {
return null;
}
} else {
return eval(funcString);
}
}
function cloneSymbol(targe) {
return Object(Symbol.prototype.valueOf.call(targe));
}
function cloneReg(targe) {
const reFlags = /\w*$/;
const result = new targe.constructor(targe.source, reFlags.exec(targe));
result.lastIndex = targe.lastIndex;
return result;
}
function cloneOtherType(target) {
const Ctor = target.constructor;
switch (Ctor) {
case Boolean:
case Number:
case String:
case Error:
case Date:
return new Ctor(target.valueOf());
case RegExp:
return cloneReg(target);
case Symbol:
return cloneSymbol(target);
case Map:
case WeakMap:
case Set:
case WeakSet:
return new Ctor([...target]);
default:
return null;
}
}
function clone (obj, hash = new WeakMap()) {
if(hash.has(obj)) return hash.get(obj)
let cloneObj = Array.isArray(obj) ? [] : {};
hash.set(obj, cloneObj);
let names = Object.getOwnPropertyNames(obj);
names.push(...Object.getOwnPropertySymbols(obj));
for (let key of names) {
cloneObj[key] = null;
if(isArrowFunction(obj[key])) cloneObj[key] = copyFunction(obj[key])
else if(isObj(obj[key])) cloneObj[key] = cloneOtherType(obj[key]);
console.log(cloneObj[key]);
if(cloneObj[key]===null) cloneObj[key] = isObj(obj[key]) ? clone(obj[key], hash) : obj[key];
}
return cloneObj;
}
//测试代码
class A {
a = 1;
}
class B extends A{
b=2;
f = ()=>{return this};
f2 = function(){return this};
date = new Date();
reg = new RegExp();
u = undefined;
s = Symbol("foo");
map = new Map();
}
var o1 = new B();
o1.map.set("1111",1);
o1[Symbol("b")] = "b";
var o =clone(o1);
Object.getOwnPropertySymbols(o)
参考文章:如何写出一个惊艳面试官的深拷贝?