深拷贝与浅拷贝
深拷贝就是将原对象的值都复制出来,在内存中重新开辟一个地址,去存放这些对象的值,且修改新对象的值,不会对之前对象的值造成修改。
如果是深拷贝拷贝的是值的引用,那么浅拷贝就是拷贝的是地址的引用,改变原对象跟新对象中的任何一个,都会对数据造成变更。
常见的浅拷贝
- object.assign
- rest修饰符(...)
- Array.prototype.concat
- Array.prototype.slice
深拷贝的实现
JSON.parse(JSON.stringify(obj))
缺点:
- 无法copy方法
- 无法解决循环引用的问题
- 无法处理Date regExp
实现一个深拷贝
function clone(target) {
if (typeof target === 'object') {
let cloneTarget = Array.isArray(target) ? [] : {};
for (const key in target) {
cloneTarget[key] = clone(target[key]);
}
return cloneTarget;
} else {
return target;
}
};
那么问题来了,这个例子可以解决下列几个问题么
- 如果拷贝的是对象是Date这样的对象,有预期的效果吗?
- 如果是循环引用呢?
深拷贝的优化
function clone(target, map = new WeakMap()) {
if (typeof target === 'object') {
const isArray = Array.isArray(target);
let cloneTarget = isArray ? [] : {};
if (map.get(target)) {
return map.get(target);
}
map.set(target, cloneTarget);
const keys = isArray ? undefined : Object.keys(target);
forEach(keys || target, (value, key) => {
if (keys) {
key = value;
}
cloneTarget[key] = clone2(target[key], map);
});
return cloneTarget;
} else {
return target;
}
}
这样就解决了循环引用的问题,但是还是无法解决一些无法遍历的对象,如Date,Error等,所以我们要对部分特殊的对象做相同的处理
/** 完整版本 */
function deepClonea(val, map = new WeakMap()) {
let type = getType(val); //当是引用类型的时候先拿到其确定的类型
if (isObj(val)) {
switch (type) {
case 'date': //日期类型重新new一次传入之前的值,date实例化本身结果不变
return new Date(val);
break;
case 'regexp': //正则类型直接new一个新的正则传入source和flags即可
return new RegExp(val.source, val.flags);
break;
case 'function': //如果是函数类型就直接通过function包裹返回一个新的函数,并且改变this指向
return new RegExp(val.source, val.flags);
break;
default:
let cloneVal = Array.isArray(val) ? [] : {};
if (map.has(val)) return map.get(val)
map.set(val, cloneVal)
for (let key in val) {
if (val.hasOwnProperty(key)) { //判断是不是自身的key
cloneVal[key] = deepClone(val[key]), map;//每一项就算是基本类型也需要走deepclone方法进行拷贝
}
}
return cloneVal;
}
} else {
return val; //当是基本数据类型的时候直接返回
}
}
function isObj(val) { //判断是否是引用类型
return (typeof val == 'object' || typeof val == 'function') && val != null
}
function getType(data) { //获取类型
var s = Object.prototype.toString.call(data);
return s.match(/\[object (.*?)\]/)[1].toLowerCase();
};
// /** 测试 */
var a = {}
a.a = a
var b = deepClonea(a)
console.log(b)