前言
已经有的工具,为什么还需要去手动实现呢?
如果这个不行了的话,就去找下一个替代品,
阅读一下文档直接用不就好了?
前辈们的付出,诚然极大地减轻了我们开发负担,
但我想编程不是一件“能运行”就完了的事。
“为什么这么写就可以达到目的?”
“这个东西到底在其中做了些什么?”
“实现需求暂且按下不表,它会有什么副作用吗?”
“知识”究竟是什么?
一个小白的学习笔记,“独行长路”取自喜欢的角色傻狗刻俄柏。
第一篇,关于深拷贝。参阅自 ConardLi 先生的文章,斗胆做了些修改:
- 类型上补充了
BigInt,和Symbol一样,存在typeof 1n === 'bigint'和typeof Object(1n) === 'object'这样的两种场景。 - 使用
Reflect.ownKeys()来处理键为Symbol的场景。 - 简化了
function的拷贝。
正文
总体思路如下:
- 首先判断值类型与引用类型,值类型的情况下直接返回。
- 引用类型下,判断是否可以遍历。不可遍历的情况下,获取其构造函数,并用构造函数创建一个新的返回。
- 可遍历的情况下,借助
Map检查其是否循环引用,若Map中已存在,则获取并返回。 - 单独处理
Map和Set类型。 - 使用
Reflect.ownKeys()遍历对象的键,递归处理其值。
代码如下:
// 使用 Object.prototype.toString.call(target) 获取的类型
// 可遍历类型
const typeMap = '[object Map]';
const typeSet = '[object Set]';
const typeArray = '[object Array]';
const typeObject = '[object Object]';
const typeArguments = '[object Arguments]';
// 不可遍历类型
const typeBoolean = '[object Boolean]';
const typeString = '[object String]';
const typeNumber = '[object Number]';
const typeBigInt = '[object BigInt]';
const typeDate = '[object Date]';
const typeError = '[object Error]';
const typeSymbol = '[object Symbol]';
const typeRegExp = '[object RegExp]';
const typeFunction = '[object Function]';
// 组装一个可遍历类型的数组,以供下文查询
const iterableTypes = [typeMap, typeSet, typeArray, typeObject, typeArguments];
// 处理 Symbol 的拷贝
// Symbol 不使用 new 关键字,可以使用 Object() 函数来创建一个 Symbol 包装器对象。
// 使用 Symbol.prototype.valueOf.call() 用以获取 Symbol 的值。
const getSymbol = function (target) {
return Object(Symbol.prototype.valueOf.call(target));
};
// 处理 RegExp 的拷贝,参阅 Lodash 的实现。
// flags 可以用 target.flags 来获取,但是参阅 MDN 该方法的支持很差,所以使用正则的方式获取。
// lastIndex 影响下一次匹配的索引,所以也要拷贝。
const getRegExp = function (target) {
const regFlags = /\w*$/;
const newReg = new RegExp(target.source, regFlags.exec(target));
newReg.lastIndex = target.lastIndex;
return newReg;
};
// 处理不可遍历类型的拷贝。
// 使用 target.constructor 来获取其构造函数并 new 一个返回。
function getUniterabletypes(target, type) {
switch (type) {
case typeBoolean:
case typeString:
case typeNumber:
case typeBigInt:
case typeDate:
case typeError:
return new target.constructor(target);
case typeSymbol:
return getSymbol(target);
case typeRegExp:
return getRegExp(target);
default:
return null;
}
}
// 判断是否为引用类型的工具函数。
function isReference(target) {
const type = typeof target;
return target !== null && (type === 'object' || type === 'function');
}
// 主函数
// 关于 Map 还是 WeakMap,ConardLi 先生的文章评论里有争论,但自己学识不足不解其究竟
// 所以听从先生“如果不懂就不要这么写”,这里使用 Map
function clone(target, map = new Map()) {
// 非引用类型
// 这里返回 boolean, number, bigint, string, date, undefined
if (!isReference(target)) return target;
// 这里对 function 直接返回,或者参阅 ConardLi 先生的文章
if (typeof target === 'function') return target;
// 引用类型
// 获取其类型
let type = Object.prototype.toString.call(target);
// 不可继续遍历
// null, Error, RegExp
// 下述包装器对象的 typeof === ‘object’,所以在判断非引用类型时会过滤到这里
// new Boolean, new Number, new String, new Date, Object(Symbol), Object(BigInt)
if (!iterableTypes.includes(type)) {
return getUniterabletypes(target, type);
}
// 可继续遍历
// map, set, array, object, args
// 使用其构造函数初始化,会相应的生成 Set, Map 等
let cloneTarget = new target.constructor();
// 检查循环引用
if (map.get(target)) return map.get(target);
map.set(target, cloneTarget);
// 处理 Set
// 注意在 add 时递归的处理其值
if (type === typeSet) {
target.forEach((value) => cloneTarget.add(clone(value, map)));
return cloneTarget;
}
// 处理 Map
// 注意在 set 时递归的处理其值
if (type === typeMap) {
target.forEach((value, key) => cloneTarget.set(key, clone(value, map)));
return cloneTarget;
}
// 遍历键,递归的处理其值
// 考虑到键为 Symbol 的场景,这里使用 Reflect.ownKeys()
// 参阅阮一峰的 ES6 在线文档:https://es6.ruanyifeng.com/#docs/reflect#Reflect-ownKeys-target
for (let key of Reflect.ownKeys(target)) {
cloneTarget[key] = clone(target[key], map);
}
return cloneTarget;
}