Object.keys(obj)
:返回自身的所有可枚举属性
Object.getOwnPropertyNames(obj)
:返回自身的所有属性,不论是否可枚举
for...in
:迭代一个对象的可枚举属性,包括继承的可枚举属性
obj.hasOwnProperty(prop)
:返回布尔值,表实例对象自身是否具有该属性(不论是否为symbol和可枚举)
只考虑普通对象
const isObject = (target) => (typeof target === 'object' || typeof target === 'function') && target !== null; // 判断是否为非null对象
function deepClone(target, map = new WeakMap()) {
if(!isObject(target)) // 不是 非null对象 直接返回
return target;
// 获取其原型
let ctor = target.constructor;
let cloneTarget = new ctor();
if(map.get(target)) // 避免死循环 eg.环形链表
return target;
map.set(target, true);
for (const prop of Object.getOwnPropertyNames(target)) {
cloneTarget[prop] = deepClone(target[prop], map); // 递归处理 深度拷贝属性
}
for (const prop of Object.getOwnPropertySymbols(target)) { // 考虑Symbol
cloneTarget[prop] = deepClone(target[prop], map);
}
// for (const prop of Reflect.ownKeys(target)) {
// cloneTarget[prop] = deepClone(target[prop], map);
// }
return cloneTarget;
}
考虑更多类型
const getType = obj => Object.prototype.toString.call(obj); // 根据toString方法转成的字符串来判断类型
const isObject = (target) => (typeof target === 'object' || typeof target === 'function') && target !== null; // 判断是否为非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; // source正则模式的字符串形式 flags修饰符
return new target.constructor(source, flags);
}
const handleFunc = (func) => { // 处理函数
if(!func.prototype) return func; // 箭头函数直接返回自身
const bodyReg = /(?<={)(.|\n)+(?=})/; // 函数体正则匹配
const paramReg = /(?<=\()(.|\n)+(?=\)\s+\{)/; // 参数正则匹配
const funcString = func.toString();
// 分别匹配 函数参数 和 函数体
const param = paramReg.exec(funcString);
const body = bodyReg.exec(funcString);
if(!body) return null; // 未匹配成功会返回 null -> false
if (param) { // 是否有参数
const paramArr = param[0].split(',');
return new Function(...paramArr, body[0]); // Function构造函数的最后一个参数才会被当作是函数体,前面的都被当作参数
} 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)) // 不是 非null对象 直接返回
return target;
const type = getType(target); // 获取类型字符串
let cloneTarget; // 存储结果
if(!canTraverse[type]) {
// 处理不能遍历的对象
return handleNotTraverse(target, type);
} else {
// 处理可遍历对象 拷贝获取其原型
let ctor = target.constructor;
cloneTarget = new ctor();
}
if(map.get(target)) // 避免死循环 eg.环形链表
return target;
map.set(target, true);
if(type === mapTag) {
//处理Map
target.forEach((item, key) => {
// (键值都进行拷贝处理 这是因为map的键值都可为任意类型)
cloneTarget.set(deepClone(key, map), deepClone(item, map)); // 递归处理
})
}
if(type === setTag) {
//处理Set
target.forEach(item => {
cloneTarget.add(deepClone(item, map)); // 递归处理
})
}
// 处理数组和对象
// for...in 可处理对象的可枚举属性 会包括继承的属性
// Object.keys() 可处理对象的可枚举属性 只包括自身的属性
// Object.getOwnPropertyNames() 处理自身属性 无论是否可枚举
// 上面的方法都无法处理 属性名为Symbol类型的属性
// Object.getOwnPropertySymbols()
// Reflect.ownKeys()
for (const prop of Object.getOwnPropertyNames(target)) {
cloneTarget[prop] = deepClone(target[prop], map); // 递归处理 深度拷贝属性
}
for (const prop of Object.getOwnPropertySymbols(target)) { // 考虑Symbol
cloneTarget[prop] = deepClone(target[prop], map);
}
// for (const prop of Reflect.ownKeys(target)) {
// cloneTarget[prop] = deepClone(target[prop], map);
// }
return cloneTarget;
}
测试:
// 对象
const obj = {
a: 1,
b: {
c: 123,
d: 465,
e: {
f: 5050
}
}
}
Object.defineProperty(obj, 'prop', {
value: '不可枚举',
enumerable: false,
configurable: true
})
const prop = Symbol(123);
obj[prop] = 'Symbol属性名';
const newObj = deepClone(obj);
// console.log('newObj: ', newObj);
// obj.a = 456
// obj.b.c = 777;
// obj[prop] = 'Symbol更改';
// console.log('newObj: ', newObj);
const objTest = {
prop: 123
}
objTest.next = objTest;
// console.log('objTest: ', objTest);
deepClone(objTest)
// console.log('deepClone(objTest): ', deepClone(objTest));
const arr = [1,2,3,obj];
const newArr = deepClone(arr);
// console.log('newArr: ', newArr);
// arr[3].a = 456;
// arr[3].b.c = 777;
// console.log('newArr: ', newArr);
// map
const map = new Map();
map.set('obj', obj);
map.set('arr', arr);
const newMap = deepClone(map);
// console.log('newMap: ', newMap);
// map.get('arr')[3].a = 456;
// map.get('arr')[3].b.c = 777;
// console.log('newMap: ', newMap);
// set
const set = new Set(arr);
const newSet = deepClone(set);
// console.log('newSet: ', newSet);
// set.add(789);
// newSet.add('ABC');
// console.log('newSet: ', newSet);
// function
let func = function (val) {
return val + '456'
};
const test = func;
const newFunc = deepClone(func);
// console.log('func.name: ', func.name); // func
// console.log('test.name: ', test.name); // func
// console.log('newFunc.name: ', newFunc.name); // anonymous
// console.log('newFunc(123): ', newFunc(123)); // 123456
// 箭头函数
const arrowFunc = (val) => val + '123';
const testArrow = arrowFunc;
const newArrowFunc = deepClone(arrowFunc);
console.log('testArrow === arrowFunc: ', testArrow === arrowFunc); // true
console.log('newArrowFunc === arrowFunc: ', newArrowFunc === arrowFunc); // true(返回本身)
console.log('newArrowFunc(456): ', newArrowFunc(456));
// Date
const date = new Date();
const newDate = deepClone(date);
console.log('date: ', date);
console.log('newDate: ', newDate);
console.log('newDate.getDay(): ', newDate.getDay());