首先回顾一下之前实现的深拷贝的代码:
function isObject(target) {
if (typeof target === 'object') {
return true
}
return false
}
function isArray(target) {
if (Array.isArray(target)) {
return true
}
return false
}
function deepClone(target) {
if (isObject(target)) {
let cloneTarget = isArray(target) ? [] : {};
for (const key in target) {
cloneTarget[key] = deepClone(target[key]);
}
return cloneTarget;
} else {
return target;
}
};
01.循环引用问题
我们看下面这个例子
testObj = {
num: 123,
}
testObj.target = testObj
let newObj = deepClone(testObj)
这段代码运行将会报错,因为循环引用造成了递归的栈溢出
C:\Projects\test\deepClone.js:18
cloneTarget[key] = deepClone(target[key]);
^
RangeError: Maximum call stack size exceeded
at deepClone (C:\Projects\test\deepClone.js:18:32)
at deepClone (C:\Projects\test\deepClone.js:18:32)
at deepClone (C:\Projects\test\deepClone.js:18:32)
at deepClone (C:\Projects\test\deepClone.js:18:32)
at deepClone (C:\Projects\test\deepClone.js:18:32)
at deepClone (C:\Projects\test\deepClone.js:18:32)
at deepClone (C:\Projects\test\deepClone.js:18:32)
at deepClone (C:\Projects\test\deepClone.js:18:32)
at deepClone (C:\Projects\test\deepClone.js:18:32)
at deepClone (C:\Projects\test\deepClone.js:18:32)
为了解决循环引用问题,我们需要一个存储容器存放当前对象和拷贝对象的对应关系(适合用key-value的数据结构进行存储,也就是map),当进行拷贝当前对象的时候,我们先查找存储容器是否已经拷贝过当前对象,如果已经拷贝过,那么直接把返回,没有的话则是继续拷贝。
这样碰到循环引用的对象的时候,可以通过存储的对应关系进行复现。
这次经过改进消除循环引用的深拷贝如下:
function deepClone(target) {
const map = new Map()
function clone (target) {
if (isObject(target)) {
let cloneTarget = isArray(target) ? [] : {};
if (map.get(target)) {
return map.get(target)
}
map.set(target,cloneTarget)
for (const key in target) {
cloneTarget[key] = clone(target[key]);
}
return cloneTarget;
} else {
return target;
}
}
return clone(target)
};
性能优化
- 据说for in的执行效率很低(好处是,可以兼容数组和对象的遍历,并且会遍历原型链上的东西),对比普通for循环也是while循环的执行效率会更高,所以我们把遍历数组的部分改成用while。object类型的可以使用object.keys获取所有的key的列表,这样就也可以用while循环的方式遍历提升效率
- 垃圾回收的效率,return之前调用map.clear也许效率会更高(没有实际测试过)
02.其他数据类型
之前我们只考虑了最基本的object和array
合理判断引用类型
之前我们判断引用类型没有考虑到null和function的情况
修改后如下
function isObject(target) {
const targetType = typeof target
return targetType!==null&&(targetType==='object'||targetType==='function')
}
获取数据类型
引用类型都有toString方法,我们通过输出的字符串可以判断具体是什么类型
function getType(target) {
return Object.prototype.toString.call(target);
}
下面是一些常用的类型
const mapTag = '[object Map]';
const setTag = '[object Set]';
const arrayTag = '[object Array]';
const objectTag = '[object Object]';
const boolTag = '[object Boolean]';
const dateTag = '[object Date]';
const errorTag = '[object Error]';
const numberTag = '[object Number]';
const regexpTag = '[object RegExp]';
const stringTag = '[object String]';
const symbolTag = '[object Symbol]';
其中可以分为可以继续遍历的类型和不可以继续遍历的类型,我们对各个类型分别做不同的处理
。。。
。。。
。。。
(过于繁琐,现在没有研究的欲望)
关于函数拷贝
没有实际的应用场景,并且实际上是无法实现完美的,因为你无法拷贝函数中的闭包。。。
总结,js的坑太多了,以后这种功能还是用lodash吧 https://github.com/lodash/lodash
人生苦短,还是别在别人挖的坑上浪费时间了,具体等碰到具体的应用场景自己自然会去学习相应的知识。
网友评论