# JSON.parse()
代码语言:javascript复制const newObj = JSON.parse(JSON.stringify(obj));
局限性:
- 无法实现对函数,正则表达式等特殊对象的克隆
- 会抛弃对象的 constructor,所有的构造函数会指向 Object
- 对象有循环引用会报错
# 简单手写版
思路:若属性为值类型,直接返回;若属性为引用类型,递归遍历。
代码语言:javascript复制function deepClone (obj) {
// 如果值 值类型 或 null ,直接返回
if (typeof obj !== 'object' || obj === null) {
return obj;
}
let copy = {};
// 如果对象是数组
if (obj.constructor === Array) {
copy = [];
}
// 遍历对象的每个属性
for (let k in obj) {
// 如果 key 是对象的自有属性
if (obj.hasOwnProperty(k)) {
// 递归调用 deepClone
copy[k] = deepClone(obj[k]);
}
}
return copy;
}
# 完整实现
# 简易版及问题
代码语言:javascript复制JSON.parse(JSON.stringify(obj));
- 无法解决 循环引用 的问题
const a = { val: 2};
a.target = a;
// 这种情况下 拷贝 会溢出
- 无法拷贝一些特殊对象,如
RegExp
,Date
,Set
,Map
等 - 无法拷贝 函数
const deepClone = (target) => {
// 如果是 值类型 或 null ,直接返回
if (typeof target !== 'object' || target === null) {
return target;
}
const copy = Array.isArray(target) ? [] : {};
for (let prop in target) {
if (target.hasOwnProperty(prop)) {
copy[prop] = deepClone(target[prop]);
}
}
return copy;
}
# 解决循环引用
代码语言:javascript复制let obj = { val: 2};
obj.target = obj;
deepClone(obj); // 报错: RangeError: Maximum call stack size exceeded
思路:创建一个 Map ,记录已经被拷贝的对象,遇到已经拷贝的对象,直接返回。
代码语言:javascript复制const isObject = (target) => {
return (typeof target === 'object' || typeof target === 'function')
&& (target !== null);
};
const deepClone = (target, map = new Map()) => {
if (map.get(target)) {
return target;
}
if (isObject(target)) {
map.set(target, true);
const cloneTarget = Array.isArray(target) ? [] : {};
for (let prop in target) {
if (target.hasOwnProperty(prop)) {
cloneTarget[prop] = deepClone(target[prop], map);
}
}
return cloneTarget;
} else {
return target;
}
};
const a = { val: 2 };
a.target = a;
const b = deepClone(a);
console.log(b); // { val: 2, target: {…} }
在 map 上的 key 和 map 构成了强引用,是一种危险操作。 被弱引用的对象可以在任何时候被回收,对于强引用,只要这个强引用还在,那么对象无法被回收。
ES6 提供了 WeakMap,可以解决这个问题。
代码语言:javascript复制const deepClone = (target, map = new WeakMap()) => {};
# 拷贝特殊对象
# 可继续遍历的对象
思路: 使用 Object.prototype.toString.call(obj)
鉴别可遍历对象
const getType = (target) => {
return Object.prototype.toString.call(target).slice(8, -1);
};
const canTraverse = (target) => {
const type = getType(target);
return ['Map', 'Set', 'Array', 'Object', 'Arguments'].includes(type);
};
const deepClone = (target, map = new WeakMap()) => {
if (!isObject(target)) {
return target;
}
let cloneTarget;
if (!canTraverse(target)) {
// TODO 处理不可遍历的对象
return;
} else {
// 确保对象原型不丢失
let ctor = target.prototype;
cloneTarget = new ctor();
}
if (map.get(target)) {
return target;
}
map.put(target, true);
if (getType(target) === 'Map') {
target.forEach((item, key) => {
cloneTarget.set(deepClone(key), deepClone(item));
});
}
if (getType(target) === 'Set') {
target.forEach((item) => {
cloneTarget.add(deepClone(item));
});
}
// 数组和对象
for (let prop in target) {
if (target.hasOwnProperty(prop)) {
cloneTarget[prop] = deepClone(target[prop]);
}
}
return cloneTarget;
};
# 不可遍历的对象
代码语言:javascript复制const canNotTraverse = (target) => {
const type = getType(target);
return ['Boolean', 'Number', 'String', 'Date', 'Error', 'RegExp', 'Function'].includes(type);
};
const handleRegExp = (target) => {
return new target.constructor(target.source, target.flags);
};
const handleFunc = (target) => {
// TODO 处理函数
};
const handleNotTraverse = (target) => {
const Ctor = target.constructor;
if (getType(target) === 'RegExp') {
return handleRegExp(target);
} else if (getType(target) === 'Function') {
return handleFunc(target);
} else {
return new Ctor(target);
}
};
# 拷贝函数
在 JS 中有两种函数,一种是普通函数,另一种是箭头函数。每个普通函数都是 Function 的实例,而箭头函数不是任何类的实例,每次调用都是不一样的引用。只需要处理普通函数的情况,箭头函数直接返回它本身就好了。利用原型来区分两者,箭头函数不存在原型。
代码语言:javascript复制const handleFunc = (func) => {
if (!func.prototype) {
return func;
}
const bodyReg = /(?<={)(.|n) (?=})/m;
const paramReg = /(?<=(). (?=)s {)/;
const funcStr = func.toString();
const param = paramReg.exec(funcStr)[0];
const body = bodyReg.exec(funcStr)[0];
if (!body) {
return null;
}
if (param) {
const paramArr = param.split(',');
return new Function(...paramArr, body);
} else {
return new Function(body);
}
};
# 完整实现
代码语言:javascript复制const getType = obj => Object.prototype.toString.call(obj);
const isObject = (target) => (typeof target === 'object' || typeof target === 'function') && target !== null;
const canTraverse = {
'[object Map]': true,
'[object Set]': true,
'[object Array]': true,
'[object Object]': true,
'[object Arguments]': true,
};
const mapTag = '[object Map]';
const setTag = '[object Set]';
const boolTag = '[object Boolean]';
const numberTag = '[object Number]';
const stringTag = '[object String]';
const symbolTag = '[object Symbol]';
const dateTag = '[object Date]';
const errorTag = '[object Error]';
const regexpTag = '[object RegExp]';
const funcTag = '[object Function]';
const handleRegExp = (target) => {
const { source, flags } = target;
return new target.constructor(source, flags);
}
const handleFunc = (func) => {
// 箭头函数直接返回自身
if(!func.prototype) return func;
const bodyReg = /(?<={)(.|n) (?=})/m;
const paramReg = /(?<=(). (?=)s {)/;
const funcString = func.toString();
// 分别匹配 函数参数 和 函数体
const param = paramReg.exec(funcString);
const body = bodyReg.exec(funcString);
if(!body) return null;
if (param) {
const paramArr = param[0].split(',');
return new Function(...paramArr, body[0]);
} else {
return new Function(body[0]);
}
}
const handleNotTraverse = (target, tag) => {
const Ctor = target.constructor;
switch(tag) {
case boolTag:
return new Object(Boolean.prototype.valueOf.call(target));
case numberTag:
return new Object(Number.prototype.valueOf.call(target));
case stringTag:
return new Object(String.prototype.valueOf.call(target));
case symbolTag:
return new Object(Symbol.prototype.valueOf.call(target));
case errorTag:
case dateTag:
return new Ctor(target);
case regexpTag:
return handleRegExp(target);
case funcTag:
return handleFunc(target);
default:
return new Ctor(target);
}
}
const deepClone = (target, map = new WeakMap()) => {
if(!isObject(target))
return target;
let type = getType(target);
let cloneTarget;
if(!canTraverse[type]) {
// 处理不能遍历的对象
return handleNotTraverse(target, type);
}else {
// 这波操作相当关键,可以保证对象的原型不丢失!
let ctor = target.constructor;
cloneTarget = new ctor();
}
if(map.get(target))
return target;
map.set(target, true);
if(type === mapTag) {
//处理Map
target.forEach((item, key) => {
cloneTarget.set(deepClone(key, map), deepClone(item, map));
})
}
if(type === setTag) {
//处理Set
target.forEach(item => {
cloneTarget.add(deepClone(item, map));
})
}
// 处理数组和对象
for (let prop in target) {
if (target.hasOwnProperty(prop)) {
cloneTarget[prop] = deepClone(target[prop], map);
}
}
return cloneTarget;
}