/**
* 深度克隆对象
* @param {*} value 需要克隆的值
* @param {WeakMap} cache 使用 WeakMap 解决环形引用问题,防止内存泄漏
*/
function deepClone(value, cache = new WeakMap()) {
// 基础数据
if(typeof value !== 'object' || value === null) {
return value;
}
// 解决环形引用问题
if(cache.has(value)) {
return cache.get(value);
}
const toString = Object.prototype.toString.call(value).slice(8,-1);
if(toString === 'Date') { // 日期时间
const result = new Date();
Object.setPrototypeOf(result, Object.getPrototypeOf(value));
return result;
} else if(toString === 'Map' || toString === 'Set') { // 集合类型
const result = toString === 'Map'? new Map() : new Set();
Object.setPrototypeOf(result, Object.getPrototypeOf(value));
value.forEach(function(item, key) {
let nkey, nval;
if(typeof key === 'object' && key !== null) {
nkey = deepClone(key, cache)
cache.set(key, nkey)
} else {
nkey = key
}
if(typeof item === 'object' && item !== null) {
nval = deepClone(item, cache)
cache.set(item, nval)
} else {
nval = item
}
toString === 'Map' ? this.set(nkey, nval) : this.add(nval)
}, result)
return result;
} else {
// 克隆结果 1.数组 2.对象
const result = Array.isArray(value) ? [] : {};
// 设置克隆结果的原型链为 value 的原型链(即保持原型一致)
Object.setPrototypeOf(result, Object.getPrototypeOf(value));
// 环形引用时将克隆的值储存到缓存中
cache.set(value, result)
for(const key in value) {
// 排除原型上的属性
if (Object.prototype.hasOwnProperty.call(value, key)) {
if(typeof value[key] === 'object' && value[key] !== null) {
result[key] = deepClone(value[key], cache)
} else {
result[key] = value[key]
}
}
}
return result;
}
}
测试例子
var date = new Date()
var dated2 = deepClone(date)
date.setFullYear(2015)
dated2.setFullYear(2018)
console.log(date);
console.log(dated2);
var map1 = new Map();
map1.set('name', '小明')
var foo = {age:18}
map1.set(foo, {nation:'汉族'})
map1.set('bar', {a:'abc'})
var map2 = deepClone(map1)
map1.set('name','张三')
var bar = map1.get('bar')
bar.a = 'def'
foo.age = 22
map1.set('bar', bar)
var foo2 = map1.get(foo)
foo2.nation = "中国"
map2.set('name','李四')
console.log(map1);
console.log(map2);