Open yygmind opened 5 years ago
一个不考虑其他数据类型的公共方法,基本满足大部分场景
function deepCopy(target, cache = new Set()) {
if (typeof target !== 'object' || cache.has(target)) {
return target
}
if (Array.isArray(target)) {
target.map(t => {
cache.add(t)
return t
})
} else {
return [...Object.keys(target), ...Object.getOwnPropertySymbols(target)].reduce((res, key) => {
cache.add(target[key])
res[key] = deepCopy(target[key], cache)
return res
}, target.constructor !== Object ? Object.create(target.constructor.prototype) : {})
}
}
主要问题是
我们另外用getOwnPropertySymbols
可以获取symbol key可以解决问题1,用集合记忆曾经遍历过的对象可以解决问题2。当然,还有很多数据类型要独立去拷贝。比如拷贝一个RegExp,lodash是最全的数据类型拷贝了,有空可以研究一下
另外,如果不考虑用symbol做key,还有两种黑科技深拷贝,可以解决环引用的问题,比stringify和parse优雅强一些
function deepCopyByHistory(target) {
const prev = history.state
history.replaceState(target, document.title)
const res = history.state
history.replaceState(prev, document.title)
return res
}
async function deepCopyByMessageChannel(target) {
return new Promise(resolve => {
const channel = new MessageChannel()
channel.port2.onmessage = ev => resolve(ev.data)
channel.port1.postMessage(target)
}).then(data => data)
}
无论哪种方法,它们都有一个共性:失去了继承关系,所以剩下的需要我们手动补上去了,故有Object.create(target.constructor.prototype)
的操作
const symbolName = Symbol();
const obj = {
objNumber: new Number(1),
number: 1,
objString: new String('ss'),
string: 'stirng',
objRegexp: new RegExp('\\w'),
regexp: /w+/g,
date: new Date(),
function: function () {},
array: [{a: 1}, 2],
[symbolName]: 111
}
obj.d = obj;
const isObject = obj => obj !== null && (typeof obj === 'object' || typeof obj === 'function');
const isFunction = obj => typeof obj === 'function'
function deepClone (obj, hash = new WeakMap()) {
if (hash.get(obj)) {
// 环处理
return hash.get(obj);
}
if (!isObject(obj)) {
return obj;
}
if (isFunction(obj)) {
// function返回原引用
return obj;
}
let cloneObj;
const Constructor = obj.constructor;
switch (Constructor) {
case Boolean:
case Date:
return new Date(+obj);
case Number:
case String:
case RegExp:
return new Constructor(obj);
default:
cloneObj = new Constructor();
hash.set(obj, cloneObj);
}
[...Object.getOwnPropertyNames(obj), ...Object.getOwnPropertySymbols(obj)].forEach(k => {
cloneObj[k] = deepClone(obj[k], hash);
})
return cloneObj;
}
const o = deepClone(obj)
console.log(o.objNumber === obj.objNumber);
console.log(o.number === obj.number);
console.log(o.objString === obj.objString);
console.log(o.string === obj.string);
console.log(o.objRegexp === obj.objRegexp);
console.log(o.regexp === obj.regexp);
console.log(o.date === obj.date);
console.log(o.function === obj.function);
console.log(o.array[0] === obj.array[0]);
console.log(o[symbolName] === obj[symbolName]);
一个不考虑其他数据类型的公共方法,基本满足大部分场景
function deepCopy(target, cache = new Set()) { if (typeof target !== 'object' || cache.has(target)) { return target } if (Array.isArray(target)) { target.map(t => { cache.add(t) return t }) } else { return [...Object.keys(target), ...Object.getOwnPropertySymbols(target)].reduce((res, key) => { cache.add(target[key]) res[key] = deepCopy(target[key], cache) return res }, target.constructor !== Object ? Object.create(target.constructor.prototype) : {}) } }
主要问题是
- symbol作为key,不会被遍历到,所以stringify和parse是不行的
- 有环引用,stringify和parse也会报错
我们另外用
getOwnPropertySymbols
可以获取symbol key可以解决问题1,用集合记忆曾经遍历过的对象可以解决问题2。当然,还有很多数据类型要独立去拷贝。比如拷贝一个RegExp,lodash是最全的数据类型拷贝了,有空可以研究一下另外,如果不考虑用symbol做key,还有两种黑科技深拷贝,可以解决环引用的问题,比stringify和parse优雅强一些
function deepCopyByHistory(target) { const prev = history.state history.replaceState(target, document.title) const res = history.state history.replaceState(prev, document.title) return res } async function deepCopyByMessageChannel(target) { return new Promise(resolve => { const channel = new MessageChannel() channel.port2.onmessage = ev => resolve(ev.data) channel.port1.postMessage(target) }).then(data => data) }
无论哪种方法,它们都有一个共性:失去了继承关系,所以剩下的需要我们手动补上去了,故有
Object.create(target.constructor.prototype)
的操作
有两个问题:
target
是一个数组,拷贝结果没有返回target
是一个函数,函数没有被深拷贝一个不考虑其他数据类型的公共方法,基本满足大部分场景
function deepCopy(target, cache = new Set()) { if (typeof target !== 'object' || cache.has(target)) { return target } if (Array.isArray(target)) { target.map(t => { cache.add(t) return t }) } else { return [...Object.keys(target), ...Object.getOwnPropertySymbols(target)].reduce((res, key) => { cache.add(target[key]) res[key] = deepCopy(target[key], cache) return res }, target.constructor !== Object ? Object.create(target.constructor.prototype) : {}) } }
主要问题是
- symbol作为key,不会被遍历到,所以stringify和parse是不行的
- 有环引用,stringify和parse也会报错
我们另外用
getOwnPropertySymbols
可以获取symbol key可以解决问题1,用集合记忆曾经遍历过的对象可以解决问题2。当然,还有很多数据类型要独立去拷贝。比如拷贝一个RegExp,lodash是最全的数据类型拷贝了,有空可以研究一下 另外,如果不考虑用symbol做key,还有两种黑科技深拷贝,可以解决环引用的问题,比stringify和parse优雅强一些function deepCopyByHistory(target) { const prev = history.state history.replaceState(target, document.title) const res = history.state history.replaceState(prev, document.title) return res } async function deepCopyByMessageChannel(target) { return new Promise(resolve => { const channel = new MessageChannel() channel.port2.onmessage = ev => resolve(ev.data) channel.port1.postMessage(target) }).then(data => data) }
无论哪种方法,它们都有一个共性:失去了继承关系,所以剩下的需要我们手动补上去了,故有
Object.create(target.constructor.prototype)
的操作有两个问题:
- 如果
target
是一个数组,拷贝结果没有返回- 如果
target
是一个函数,函数没有被深拷贝
数组的确是我忘了写return了。然后拷贝函数这种操作平时真不会有人做。如果实在是要拷贝,除了简单的function.toString和正则匹配外,还要考虑箭头函数、参数默认值、换行、this、函数名字
new obj.constructor()
或eval(obj.toString())
创建一个新实例temp,并保存进map,通过Object.getOwnPropertyNames
和Object.getOwnPropertySymbols
遍历obj的所有属性名,递归调用deepClone完成temp上所有属性的声明和赋值,最后返回tempfunction deepClone(obj, map = new WeakMap()) {
const type = typeof obj;
if (obj === null || type !== 'function' && type !== 'object') return obj;
if (obj instanceof Date) return Date(obj);
if (obj instanceof RegExp) return RegExp(obj);
if (map.has(obj)) return map.get(obj);
const temp = type === 'function' ? eval(obj.toString()) : new obj.constructor();
map.set(obj, temp);
Object.getOwnPropertyNames(obj)
.concat(Object.getOwnPropertySymbols(obj))
.forEach((i) => {
temp[i] = deepClone(obj[i], map);
});
return temp;
}
函数拷贝的情况太复杂了,所以就直接用了eval(obj.toString())
写个不用递归用循环的方式实现的版本吧
const getType = obj => Object.prototype.toString.call(obj).match(/\[object\s(.*)]/)[1]
function deepClone(obj) {
let res = {}
let stack = []
let root = {
parent: obj,
prop: null,
data: res
}
let wm = new WeakMap()
stack.push(root)
while (stack.length) {
let item = stack.pop()
Reflect.ownKeys(item.parent).forEach(key => {
if (wm.get(item.parent[key])) {
item.data[key] = wm.get(item.parent[key])
return
}
switch (getType(item.parent[key])) {
case 'Object': {
item.data[key] = {}
stack.push({
parent: item.parent[key],
prop: key,
data: item.data[key]
})
wm.set(item.parent[key], item.parent[key])
break
}
case 'Array': {
item.data[key] = []
stack.push({
parent: item.parent[key],
prop: key,
data: item.data[key]
})
wm.set(item.parent[key], item.parent[key])
break
}
case 'Date': {
item.data[key] = new Date(item.parent[key])
break
}
case 'RegExp': {
item.data[key] = new RegExp(item.parent[key])
break
}
default: {
item.data[key] = item.parent[key]
}
}
})
}
return res
}
let obj = {
num: 0,
str: '',
boolean: true,
unf: undefined,
nul: null,
obj: {
name: '我是一个对象',
id: 1,
qwe: {
a: 1
}
},
arr: [0, 1, 2, {b: 2}],
date: new Date(0),
reg: /我是一个正则/ig,
[Symbol('1')]: 1,
func() {
console.log(123)
}
};
obj.loop = obj
let cloneObj = deepClone(obj);
console.log('obj', obj);
console.log('cloneObj', cloneObj);
// 对比两个对象引用类型的值是相同
Object.keys(cloneObj).filter(key => key !== 'nul').forEach(key => {
if (typeof cloneObj[key] === 'object' || typeof cloneObj[key] === 'function') {
console.log(`${key}相同吗? `, cloneObj[key] === obj[key])
}
})
函数拷贝不了,还有一些奇奇怪怪的引用类型也拷贝不了,一般情况应该没啥问题,其实拷贝函数有一种思路是用AST(手动狗头)
function deepClone(obj, hash = new WeakMap()) { if (hash.has(obj)) return obj; var cobj; // null if (obj === null) { return obj } let t = typeof obj; // 基本类型 switch (t) { case 'string': case 'number': case 'boolean': case 'undefined': return obj; } // 数组 if (Array.isArray(obj)) { cobj = []; obj.forEach((c, i) => { cobj.push(deepClone(obj[i])) }); } else { cobj = {}; // object // symbol if (Object.prototype.toString.call(obj) === "[object Object]") { Object.getOwnPropertyNames(obj).concat(Object.getOwnPropertySymbols(obj)).forEach(c => { hash.set(obj, obj); cobj[c] = deepClone(obj[c], hash); }); } else { //内置Object cobj = obj; } } return cobj; }
var a=[]; a.push(a); deepClone(a);
缺点:无法拷贝函数 、Symbol
const deepClone = function (obj) {
var str = JSON.stringify(obj);
return JSON.parse(str);
}
Symbol 不是独一无二的吗?还能拷贝?
函数为什么要拷贝?函数不是用来复用的吗?
@lhyt
function clone(param) {
// 数组 、 对象 、 普通值
let res = null
let type = Object.prototype.toString.call(param)
if (type === '[object Object]') {
res = {}
for (const key in param) {
res[key] = clone(param[key])
}
} else if (type === '[object Array]') {
res = []
param.forEach((item, index) => {
res[index] = clone(item)
})
} else {
res = param
}
return res
}
let o = { fn: () => { }, name: 1, o: { a: [1], name: 2, fn: () => { } }, arr: [1, 2, 3], s: Symbol() }
let o2 = clone(o)
o2.name = 200
console.log(o , o2)
分享一个来自 Vuex 的 deepCopy 解决了循环引用,cache 存储所有嵌套 obj 及其 copy 副本,以 cache 中是否有某个嵌套 obj 来判断是否循环引用,有则返回 引用的 copy
export function deepCopy (obj, cache = []) {
// just return if obj is immutable value
if (obj === null || typeof obj !== 'object') {
return obj
}
// if obj is hit, it is in circular structure
const hit = find(cache, c => c.original === obj)
if (hit) {
return hit.copy
}
const copy = Array.isArray(obj) ? [] : {}
// put the copy into cache at first
// because we want to refer it in recursive deepCopy
cache.push({
original: obj,
copy
})
Object.keys(obj).forEach(key => {
copy[key] = deepCopy(obj[key], cache)
})
return copy
}
为什么没人用getOwnPropertyDescriptors
@lhyt 的
function deepCopy(target, cache = new Set()) { if (typeof target !== 'object' || cache.has(target)) { return target } if (Array.isArray(target)) { target.map(t => { cache.add(t) return t }) } else { return [...Object.keys(target), ...Object.getOwnPropertySymbols(target)].reduce((res, key) => { cache.add(target[key]) res[key] = deepCopy(target[key], cache) return res }, target.constructor !== Object ? Object.create(target.constructor.prototype) : {}) } }
在这种情况下有问题
let ooo = {
a: {}
};
ooo.a.c = ooo;
let eee = deepCopy(ooo)
console.log(eee.a.c === eee);
console.log(eee.a.c === ooo);
感觉环状数据之前这个里面 https://github.com/Advanced-Frontend/Daily-Interview-Question/issues/10 处理的方法可以
const pub = new (class {
// static obj = {};
constructor() {}
deepCopy(obj) {
let result = Array.isArray(obj) ? [] : {};
// 获取到当前层对象的所有属性。
let ownProperty = [...Object.keys(obj), ...Object.getOwnPropertySymbols(obj)];
for (let i in ownProperty) {
if (obj.hasOwnProperty(ownProperty[i])) {
// console.log(ownProperty[i], ':', obj[ownProperty[i]]);
if (typeof obj[ownProperty[i]] === 'object' && obj[ownProperty[i]] != null) {
result[ownProperty[i]] = this.deepCopy(obj[ownProperty[i]]);
} else {
result[ownProperty[i]] = obj[ownProperty[i]];
}
}
}
return result;
}
})();
const HEAD = Symbol('头顶');
const HAT = Symbol('帽子');
const CAUSE = Symbol('原因');
// 新建一个魔性的对象。
let obj = {
origin: { name: '小明', [HEAD]: '🍃' },
[HAT]: { [CAUSE]: 'wife', color: 'green', background: '🌾', num: [1, 2, 3, 4, 5, 6, 7, 8, 9, [Infinity]] },
move: function() {}
};
// 接下来对这个魔性的对象进行深拷贝,太残忍了。
let objCopy = pub.deepCopy(obj);
// 验证
obj[HAT].num[1] = 0;
console.log(obj, objCopy);
console.log('obj:', obj[HAT].num[1], ', objCopy:', objCopy[HAT].num[1]); // obj: 0 , objCopy: 2
不考虑正则、函数等奇怪类型的拷贝,满足大多数深度拷贝需求 定制需求如下: 1、循环引用 2、Symbol 类型拷贝
function deepClone(val,map = new WeakMap()){
if(val === null || typeof val !=='object') return val;
//循环引用
if(map.has(val)) return map.get(val);
let clone = Array.isArray(val) ? [] : {};
map.set(val,clone);
// 获取对象中所有的属性名(包含Symbol值)
let keys = Reflect.ownKeys(val);(可换为:Object.keys(val).concat(Object.ownPropertySymbols(val)))
let len = keys.length;
while(len--){
clone[keys[len]] = deepClone(val[keys[len]],map);
}
return clone;
}
欢迎交流
const deepCopy = (data, map = new Map()) => {
let result
if (map.has(data)) {
return map.get(data)
} else if (data instanceof Array) {
result = []
map.set(data, result)
data.forEach(item => result.push(deepCopy(item, map)))
} else if (typeof data === 'object') {
result = {}
map.set(data, result)
for (let k of Reflect.ownKeys(data)) {
result[k] = deepCopy(data[k], map)
}
} else {
result = data
}
return result
}
记录一下简单做的
let objc = {
a: 1,
b: Symbol('2'),
c:{
d: Symbol('3'),
e: {
f: Symbol('4'),
}
}
};
function deepClone(obj) {
let result = {}
for (const key in obj) {
typeof (obj[key]) == 'object' ?
result[key] = deepClone(obj[key]) :
result[key] = obj[key]
}
return result
}
let objct = deepClone(objc)
objct.c.e.f = 2
console.log(objc);
console.log(objct);
function type(data) {
let type = Object.prototype.toString.call(data)
if (type === '[object Array]') {
return []
} else if (type === '[object Object]') {
return {}
} else {
return data
}
}
function deepClone(data) {
let map = new Map() // 处理环状
let deepCloneFunc = function (data) {
let result = type(data)
if (map.get(data)) { //处理环状
result = data
return result
}
if (result !== data) { // 不是基本数据类型
map.set(data, result) // 为了判断该对象是否出现过,处理环状
const objectSymbolsKey = Object.getOwnPropertySymbols(data) // 普通遍历key是获取不到key 为Symbol的
if (objectSymbolsKey.length) {
for (let i in objectSymbolsKey) {
result[objectSymbolsKey[i]] = deepCloneFunc(data[objectSymbolsKey[i]])
}
}
for (let key in data) {
result[key] = deepCloneFunc(data[key])
}
return result
} else {
return data
}
}
return deepCloneFunc(data)
}
let objx ={}
objx.repeat = objx
let obj = {
[Symbol('name')]: 'litokele',
gender: Symbol('male'),
age: 18,
favoriteAnime: ['xxx1', 'xxx2'],
obj: {
[Symbol('test')]: 'test',
name: 'kele',
age: 18
},
repeat: objx
}
let myObj = deepClone(obj)
console.log("my_ojb:", myObj)
var deepClone = (target, hash = new WeakMap) => {
if (target == null) return target
if (typeof target !== 'object') return target
if (target instanceof RegExp) return new RegExp(target)
if (target instanceof Date) return new Date(target)
if (hash.has(target)) return hash.get(target)
var instance = new target.constructor
hash.set(target, instance)
Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target)).forEach(key => {
instance[key] = deepClone(target[key], hash)
})
return instance
}
function cloneObj(obj) { if (obj === null) return null; if (typeof obj !== 'object') return obj; if (obj.constructor === Date) return new Date(obj); if (obj.constructor === RegExp) return new RegExp(obj); var newObj = new obj.constructor(); for (var key in obj) { if (obj.hasOwnProperty(key)) { var val = obj[key]; newObj[key] = typeof val === 'object' ? cloneObj(val) : val; } } return newObj; };
流下了没有技术的眼泪...
少了个find
函数
const find = (cache, fn) => cache.filter(fn)[0]
分享一个来自 Vuex 的 deepCopy 解决了循环引用,cache 存储所有嵌套 obj 及其 copy 副本,以 cache 中是否有某个嵌套 obj 来判断是否循环引用,有则返回 引用的 copy
export function deepCopy (obj, cache = []) { // just return if obj is immutable value if (obj === null || typeof obj !== 'object') { return obj } // if obj is hit, it is in circular structure const hit = find(cache, c => c.original === obj) if (hit) { return hit.copy } const copy = Array.isArray(obj) ? [] : {} // put the copy into cache at first // because we want to refer it in recursive deepCopy cache.push({ original: obj, copy }) Object.keys(obj).forEach(key => { copy[key] = deepCopy(obj[key], cache) }) return copy }
// 咱来个简单易懂的
function deepClone(obj) {
let newObj = Array.isArray(obj) ? [...obj] : {...obj}
Reflect.ownKeys(obj).forEach(o => {
if(obj[o] && typeof obj[o] === 'object') {
newObj[o] = deepClone(obj[o])
} else {
newObj[o] = obj[o]
}
})
return newObj
}
参考上面几位大佬的答案整理了一下
function deepClone(target, cache = []) {
if (typeof target !== 'object') {
return target
}
let hit = cache.filter(item => item.origin === target)[0];
if (hit) {
return hit.copy;
}
if (Array.isArray(target)) {
return target.map(item => {
return deepClone(item, cache);
})
}
let copy = target.constructor === Object ? {} : Object.create(target.constructor.prototype)
cache.push({
origin: target,
copy
})
return [...Object.keys(target), ...Object.getOwnPropertySymbols(target)].reduce((clone, key) => {
clone[key] = deepClone(target[key], cache);
return clone;
}, copy)
}
function deepClone(obj, hash = new WeakMap()) { if (hash.has(obj)) return obj; var cobj; // null if (obj === null) { return obj } let t = typeof obj; // 基本类型 switch (t) { case 'string': case 'number': case 'boolean': case 'undefined': return obj; } // 数组 if (Array.isArray(obj)) { cobj = []; obj.forEach((c, i) => { cobj.push(deepClone(obj[i])) }); } else { cobj = {}; // object // symbol if (Object.prototype.toString.call(obj) === "[object Object]") { Object.getOwnPropertyNames(obj).concat(Object.getOwnPropertySymbols(obj)).forEach(c => { hash.set(obj, obj); cobj[c] = deepClone(obj[c], hash); }); } else { //内置Object cobj = obj; } } return cobj; }
不知道有没有漏的。。 你处理循环引用错了。
function nativeDeepCopy (origin, hash = new WeakMap()) {
const type = typeof origin
let clone
if (origin === null || type !== 'function' && type !== 'object') return origin
if (hash.has(origin)) return hash.get(origin)
try {
clone = new origin.constructor()
} catch(e) {
clone = Object.create(Object.getPrototypeOf(origin))
}
hash.set(origin, clone)
let keys = [...Object.getOwnPropertyNames(origin), ...Object.getOwnPropertySymbols(origin)]
for (let i = 0; i < keys.length; i++) {
let descriptor = Object.getOwnPropertyDescriptor(origin, keys[i])
descriptor.value = nativeDeepCopy(descriptor.value, hash)
Object.defineProperty(clone, keys[i], descriptor)
}
return clone
}
记录一下
const init = (origin) => {
let type = Object.prototype.toString.call(origin)
if (type === '[object Array]') {
return []
} else if (type === '[object Object]') {
return {}
}
}
const deepCopy = (origin, map = new WeakMap()) => {
const type = typeof origin
if(!type || (type !== 'function' && type !== 'object')) return origin
if(map.has(origin)) return map.get(origin) // 相互引用
if(type == 'function') return origin.bind(null)
let clone = init(origin)
map.set(origin, clone)
const opk = Object.getOwnPropertyNames(origin)
const osk = Object.getOwnPropertySymbols(origin) // Symbol
const keys = [...opk, ...osk]
for(let i of keys){
clone[i] = deepCopy(origin[i], map)
}
return clone
}
let circle = {}
circle.repeat = circle
let obj = {
[Symbol('name')]: '7yue',
age: 26,
gender: Symbol('male'),
favor: ['fav1', 'fav2'],
say: function(){},
friends: {
p1: { [Symbol('name')]: 'p1', age: 24, gender: Symbol('male')},
p2: { [Symbol('name')]: 'p1', age: 27, gender: Symbol('male')}
},
repeat: circle
}
let c = deepCopy(obj)
写个不用递归用循环的方式实现的版本吧
const getType = obj => Object.prototype.toString.call(obj).match(/\[object\s(.*)]/)[1] function deepClone(obj) { let res = {} let stack = [] let root = { parent: obj, prop: null, data: res } let wm = new WeakMap() stack.push(root) while (stack.length) { let item = stack.pop() Reflect.ownKeys(item.parent).forEach(key => { if (wm.get(item.parent[key])) { item.data[key] = wm.get(item.parent[key]) return } switch (getType(item.parent[key])) { case 'Object': { item.data[key] = {} stack.push({ parent: item.parent[key], prop: key, data: item.data[key] }) wm.set(item.parent[key], item.parent[key]) break } case 'Array': { item.data[key] = [] stack.push({ parent: item.parent[key], prop: key, data: item.data[key] }) wm.set(item.parent[key], item.parent[key]) break } case 'Date': { item.data[key] = new Date(item.parent[key]) break } case 'RegExp': { item.data[key] = new RegExp(item.parent[key]) break } default: { item.data[key] = item.parent[key] } } }) } return res } let obj = { num: 0, str: '', boolean: true, unf: undefined, nul: null, obj: { name: '我是一个对象', id: 1, qwe: { a: 1 } }, arr: [0, 1, 2, {b: 2}], date: new Date(0), reg: /我是一个正则/ig, [Symbol('1')]: 1, func() { console.log(123) } }; obj.loop = obj let cloneObj = deepClone(obj); console.log('obj', obj); console.log('cloneObj', cloneObj); // 对比两个对象引用类型的值是相同 Object.keys(cloneObj).filter(key => key !== 'nul').forEach(key => { if (typeof cloneObj[key] === 'object' || typeof cloneObj[key] === 'function') { console.log(`${key}相同吗? `, cloneObj[key] === obj[key]) } })
函数拷贝不了,还有一些奇奇怪怪的引用类型也拷贝不了,一般情况应该没啥问题,其实拷贝函数有一种思路是用AST(手动狗头)
function deepclone(obj,hash = new WeakMap()){ if(hash.has(obj)) return hash.get(obj) if(Array.isArray(obj)){ return obj.reduce((res,v)=>{ res.push(deepclone(v,hash)) return res },[]) }else if(obj instanceof Object){ let nObj = {} hash.set(obj,nObj) Object.getOwnPropertyNames(obj).concat(Object.getOwnPropertySymbols(obj)).forEach(key => { nObj[key] = deepclone(obj[key],hash) }) return nObj } return obj }
来源于拉勾JavaScript核心原理精讲课程。
const isComplexDataType = (obj) =>
(typeof obj === "object" || typeof obj === "function") && obj !== null;
const deepClone = function (obj, hash = new WeakMap()) {
if (obj.constructor === Date) return new Date(obj); // 日期对象直接返回一个新的日期对象
if (obj.constructor === RegExp) return new RegExp(obj); //正则对象直接返回一个新的正则对象
//如果循环引用了就用 weakMap 来解决
if (hash.has(obj)) return hash.get(obj);
// getOwnPropertyDescriptors方法返回指定对象上一个自有属性对应的属性描述符
let allDesc = Object.getOwnPropertyDescriptors(obj);
//遍历传入参数所有键的特性,Object.create方法创建一个新对象,使用现有的对象来提供新创建对的__proto__
let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc);
//继承原型链
hash.set(obj, cloneObj);
for (let key of Reflect.ownKeys(obj)) {
cloneObj[key] =
isComplexDataType(obj[key]) && typeof obj[key] !== "function"
? deepClone(obj[key], hash)
: obj[key];
}
return cloneObj;
};
// 下面是验证代码
let obj = {
num: 0,
str: "",
boolean: true,
unf: undefined,
nul: null,
obj: { name: "我是一个对象", id: 1 },
arr: [0, 1, 2],
func: function () {
console.log("我是一个函数");
},
date: new Date(0),
reg: new RegExp("/我是一个正则/ig"),
[Symbol("1")]: 1,
};
Object.defineProperty(obj, "innumerable", {
enumerable: false,
value: "不可枚举属性",
});
obj = Object.create(obj, Object.getOwnPropertyDescriptors(obj));
obj.loop = obj; // 设置loop成循环引用的属性
let cloneObj = deepClone(obj);
cloneObj.arr.push(4);
console.log("obj", obj);
console.log("cloneObj", cloneObj);
function deepClone(src) {
return recur(src)
function recur(src) {
const result = Array.isArray(src) ? [] : {};
const loopArr = Array.isArray(src) ? Array.from({ length: src.length }, (x, i) => i) : Object.keys(src)
for (let i = 0; i < loopArr.length; i++) {
switch (typeof src[loopArr[i]]) {
case 'object':
result[loopArr[i]] = recur(src[loopArr[i]]);
break;
default:
result[loopArr[i]] = src[loopArr[i]];
}
}
return result;
}
}
const src = {
arr: [1, 2, 3],
obj: { a: 'a' },
fun() {
return 'fun'
},
num: 1,
str: 'a',
boo: false
}
const des = deepClone(src);
src.arr.shift();
src.arr.push(4);
src.obj.a = 'A'
console.log("debug-", src, des);
var a = {
a: '1',
b: [1, 2, 3, 4, 5, { t: 'test' }],
[Symbol()]: 'symbol',
c: new Date(),
r: /\d/
}
a.c = { b: a }
function deepClone(obj, hash = new WeakMap()) {
if(hash.has(obj)) return hash.get(obj);
if(obj === null || typeof obj !== 'object') return obj;
let copy;
if(Array.isArray(obj)) {
copy = [];
for(const v of obj) {
copy.push(deepClone(v));
}
} else if(Object.prototype.toString.call(obj) === '[object Object]') {
copy = {};
// 考虑循环引用的情况
hash.set(obj, copy);
Object.getOwnPropertyNames(obj).concat(Object.getOwnPropertySymbols(obj)).forEach((k) => {
copy[k] = deepClone(obj[k], hash);
});
} else {
return obj;
}
return copy;
}
var b = deepClone(a);
console.log(a);
console.log(b);
console.log(a.c.b === b.c.b); // false
console.log(b.c.b === b); // true
function deepClone(target, cache = new WeakMap()) {
// 循环引用
if(cache.has(target)) {
return cache.get(target);
}
// 基本类型,函数
if(typeof target !== 'object') {
return target;
}
if(target === null) return null;
// 数组
if(Array.isArray(target)) {
let newArr = [];
target.forEach((item,index) => {
newArr[index] = deepClone(item, cache)
});
return newArr;
}
let newObj;
// 普通对象
if(target.toString() === '[object Object]') {
newObj = {};
cache.set(target, newObj);
[...Object.keys(target),...Object.getOwnPropertySymbols(target)].forEach(key => {
newObj[key] = deepClone(target[key], cache);
});
return newObj;
} else {
return target;
}
}
let obj = {
num: 0,
str: '',
boolean: true,
unf: undefined,
nul: null,
obj: {
name: '我是一个对象',
id: 1,
qwe: {
a: 1
}
},
arr: [0, 1, 2, {b: 2}],
date: new Date(0),
reg: /我是一个正则/ig,
[Symbol('1')]: 1,
func() {
console.log(123);
}
};
obj.loop = obj;
let cloneObj = deepClone(obj);
obj.obj.id = 2;
console.log('obj', obj, obj.obj.id);
console.log('cloneObj', cloneObj, cloneObj.obj.id);
使用深度优先遍历、WeakSet来实现。 注意:对象中Symbol属性名,必须用Symbol.for()的形式来作为属性名,而不能用Symbol()来做属性名,因为Symbol()的形式,根据唯一的特性,对象自身都无法访问该属性。
var deepClone = function (target, weakset = new WeakSet()) {
if (typeof target === 'object' && target !== null) { //包括数组、对象、类数组,Set、WeakSet、Map、WeakMap
var o = Array.isArray(target) ? [] : {};
if (weakset.has(target)) {
return target;
} else {
weakset.add(target);
for (let prop in target) {
o[prop] = deepClone(target[prop], weakset)
}
var targetSymArr = Object.getOwnPropertySymbols(target);//获取target对象中的Symbol属性
if(targetSymArr.length > 0){
targetSymArr.forEach(item => {
o[item] = deepClone(target[item], weakset)
})
console.log(targetSymArr)
}
return o;
}
} else if (typeof target === "function") {
return eval('(' + target.toString() + ')')
} else {
return target
}
}
var obj = {
a: 'a',
m: {
x: '12'
}
}
var target1 = {
a: 1,
b: 'a',
c: function () {
console.log('c')
},
d: [1, 2, 3],
f: obj,
[Symbol.for('1')]: 1
}
target1.o = target1; //必须通过这种方式产生循环引用。如果在申明对象时,写在对象结构体中,无法赋值,值为undefined
var result = deepClone(target1)
console.log(result)
// 获取数据类型
function getType(o) {
return Object.prototype.toString.call(o).slice(8, -1);
}
// 判断是否是对象
function isObject(o) {
return o !== null && (typeof o == "object" || typeof o == "function");
}
// 可遍历的对象列表
let list = ["Object", "Array", "Arguments", "Set", "Map"];
// 初始化可遍历的对象
function initCloneTarget(target) {
return new target.constructor();
}
// 处理不可遍历的对象
function returnCloneTarget(target, type) {
let _constructor = target.constructor;
switch (type) {
case "Error":
case "Date":
case "String":
case "Number":
case "Boolean":
return new _constructor(target.valueOf());
case "RegExp":
return cloneReg(target);
case "Symbol":
return cloneSymbol(target);
case "Function":
return cloneFunction(target);
default:
return null;
}
}
// 拷贝 symbol 对象
function cloneSymbol(target) {
return Object(target.valueOf());
}
// 拷贝正则对象
function cloneReg(target) {
const reFlags = /\w*$/;
const result = new RegExp(target.source, reFlags.exec(target));
result.lastIndex = target.lastIndex;
return result;
}
// 拷贝函数对象
function cloneFunction(target) {
return new Function(`return (${target})()`);
}
// 深拷贝函数
function deepClone(target, map = new WeakMap()) {
// 如果是基本类型,直接返回
if (!isObject(target)) return target;
let type = getType(target);
let cloneTarget;
// 如果是可以遍历的对象,则进行初始化
if (list.includes(type)) {
cloneTarget = initCloneTarget(target);
}
// 否则就返回实例副本
else {
return returnCloneTarget(target, type);
}
// 解决循环引用
if (map.has(target)) return map.get(target);
map.set(target, cloneTarget);
// 如果是 set
if (type === "Set") {
target.forEach((value) => {
cloneTarget.add(deepClone(value, map));
});
}
// 如果是 map
if (type === "Map") {
target.forEach((value, key) => {
cloneTarget.set(key, deepClone(value, map));
});
}
// 如果是对象字面量、数组或者类数组对象
if (type === "Object" || type === "Array" || type === "Arguments") {
Reflect.ownKeys(target).forEach((key) => {
cloneTarget[key] = deepClone(target[key], map);
});
}
return cloneTarget;
}
考虑了循环引用以及Symbol key,未考虑特殊对象(RegExp,Date等) 话说vscode居然把循环引用通过控制台的方式打印出来了,强。。。
function deepCopy(target, mem = new WeakMap()) {
if ((target === null) || (typeof target !== 'object')) {
return target;
}
if (mem.has(target)) {
return mem.get(target);
}
if (Array.isArray(target)) {
return target.map(item => {
return deepCopy(item, mem);
});
} else {
const copied = (target.constructor !== Object) ? Object.create(target.constructor.prototype) : {};
mem.set(target, copied);
[...Object.getOwnPropertyNames(target), ...Object.getOwnPropertySymbols(target)].reduce((currentObj, key) => {
currentObj[key] = deepCopy(target[key], mem);
return currentObj;
}, copied);
return copied;
}
}
const a = {
b: [1,2,3,4],
c: 'str',
d: {
e: 1,
f: Symbol(233)
},
[Symbol(234)]: 'g'
}
a.d.g = a;
a.b.push(a);
let b = deepCopy(a);
console.log(a);
console.log(b);
console.log(a.d === b.d);
结果:
var cloneDeep = function(data, map = new WeakMap()){
if(!data) null;
let result;
if(typeof data == 'object'){
result = Object.prototype.toString.call(data) == '[object Object]' ? {} : [];
if(map.get(data)){
return map.get(data);
}
map.set(data, result);
let temp = Reflect.ownKeys(data);
for(let key of temp){
result[key] = cloneDeep(data[key], map);
}
} else {
result = data;
}
return result;
}
var obj = {
a: 1,
b: {
c: 2,
d: {
e: 3
}
},
f: undefined,
[Symbol('g')]: 4,
h: Symbol('i')
}
obj.f = obj;
var cloneObj = cloneDeep(obj);
写个不用递归用循环的方式实现的版本吧
const getType = obj => Object.prototype.toString.call(obj).match(/\[object\s(.*)]/)[1] function deepClone(obj) { let res = {} let stack = [] let root = { parent: obj, prop: null, data: res } let wm = new WeakMap() stack.push(root) while (stack.length) { let item = stack.pop() Reflect.ownKeys(item.parent).forEach(key => { if (wm.get(item.parent[key])) { item.data[key] = wm.get(item.parent[key]) return } switch (getType(item.parent[key])) { case 'Object': { item.data[key] = {} stack.push({ parent: item.parent[key], prop: key, data: item.data[key] }) wm.set(item.parent[key], item.parent[key]) break } case 'Array': { item.data[key] = [] stack.push({ parent: item.parent[key], prop: key, data: item.data[key] }) wm.set(item.parent[key], item.parent[key]) break } case 'Date': { item.data[key] = new Date(item.parent[key]) break } case 'RegExp': { item.data[key] = new RegExp(item.parent[key]) break } default: { item.data[key] = item.parent[key] } } }) } return res } let obj = { num: 0, str: '', boolean: true, unf: undefined, nul: null, obj: { name: '我是一个对象', id: 1, qwe: { a: 1 } }, arr: [0, 1, 2, {b: 2}], date: new Date(0), reg: /我是一个正则/ig, [Symbol('1')]: 1, func() { console.log(123) } }; obj.loop = obj let cloneObj = deepClone(obj); console.log('obj', obj); console.log('cloneObj', cloneObj); // 对比两个对象引用类型的值是相同 Object.keys(cloneObj).filter(key => key !== 'nul').forEach(key => { if (typeof cloneObj[key] === 'object' || typeof cloneObj[key] === 'function') { console.log(`${key}相同吗? `, cloneObj[key] === obj[key]) } })
函数拷贝不了,还有一些奇奇怪怪的引用类型也拷贝不了,一般情况应该没啥问题,其实拷贝函数有一种思路是用AST(手动狗头)
为什么要使用weakmap?求解
https://juejin.cn/post/6990274836278804517#heading-7 深拷贝一定要考虑循环引用及爆栈等问题,可以参考这个
function copyObj(obj) { const res = {}; Reflect.ownKeys(obj).forEach((key) => { if (typeof obj[key] === "object") { res[key] = copyObj(obj[key]); if (Array.isArray(obj[key])) { res[key].length = obj[key].length; res[key] = Array.from(res[key]); } } else { res[key] = obj[key]; } }); return res; } 不知道这个怎么样
function deepClone(target, weakMap = new WeakMap()) {
if (typeof target === null || typeof target !== "object") {
return target;
}
if (target instanceof RegExp) return new RegExp(target);
if (target instanceof Date) return new Date(target);
if (weakMap.has(target)) return weakMap.get(target);
let copy = Array.isArray(target) ? [] : {};
weakMap.set(target, copy);
const list = [
...Object.keys(target),
...Object.getOwnPropertySymbols(target),
];
for (const key of list) {
copy[key] = deepClone(target[key], weakMap);
}
return copy;
}
一个不考虑其他数据类型的公共方法,基本满足大部分场景
function deepCopy(target, cache = new Set()) { if (typeof target !== 'object' || cache.has(target)) { return target } if (Array.isArray(target)) { target.map(t => { cache.add(t) return t }) } else { return [...Object.keys(target), ...Object.getOwnPropertySymbols(target)].reduce((res, key) => { cache.add(target[key]) res[key] = deepCopy(target[key], cache) return res }, target.constructor !== Object ? Object.create(target.constructor.prototype) : {}) } }
主要问题是
- symbol作为key,不会被遍历到,所以stringify和parse是不行的
- 有环引用,stringify和parse也会报错
我们另外用
getOwnPropertySymbols
可以获取symbol key可以解决问题1,用集合记忆曾经遍历过的对象可以解决问题2。当然,还有很多数据类型要独立去拷贝。比如拷贝一个RegExp,lodash是最全的数据类型拷贝了,有空可以研究一下另外,如果不考虑用symbol做key,还有两种黑科技深拷贝,可以解决环引用的问题,比stringify和parse优雅强一些
function deepCopyByHistory(target) { const prev = history.state history.replaceState(target, document.title) const res = history.state history.replaceState(prev, document.title) return res } async function deepCopyByMessageChannel(target) { return new Promise(resolve => { const channel = new MessageChannel() channel.port2.onmessage = ev => resolve(ev.data) channel.port1.postMessage(target) }).then(data => data) }
无论哪种方法,它们都有一个共性:失去了继承关系,所以剩下的需要我们手动补上去了,故有
Object.create(target.constructor.prototype)
的操作一个不考虑其他数据类型的公共方法,基本满足大部分场景
function deepCopy(target, cache = new Set()) { if (typeof target !== 'object' || cache.has(target)) { return target } if (Array.isArray(target)) { target.map(t => { cache.add(t) return t }) } else { return [...Object.keys(target), ...Object.getOwnPropertySymbols(target)].reduce((res, key) => { cache.add(target[key]) res[key] = deepCopy(target[key], cache) return res }, target.constructor !== Object ? Object.create(target.constructor.prototype) : {}) } }
主要问题是
- symbol作为key,不会被遍历到,所以stringify和parse是不行的
- 有环引用,stringify和parse也会报错
我们另外用
getOwnPropertySymbols
可以获取symbol key可以解决问题1,用集合记忆曾经遍历过的对象可以解决问题2。当然,还有很多数据类型要独立去拷贝。比如拷贝一个RegExp,lodash是最全的数据类型拷贝了,有空可以研究一下另外,如果不考虑用symbol做key,还有两种黑科技深拷贝,可以解决环引用的问题,比stringify和parse优雅强一些
function deepCopyByHistory(target) { const prev = history.state history.replaceState(target, document.title) const res = history.state history.replaceState(prev, document.title) return res } async function deepCopyByMessageChannel(target) { return new Promise(resolve => { const channel = new MessageChannel() channel.port2.onmessage = ev => resolve(ev.data) channel.port1.postMessage(target) }).then(data => data) }
无论哪种方法,它们都有一个共性:失去了继承关系,所以剩下的需要我们手动补上去了,故有
Object.create(target.constructor.prototype)
的操作
对象里的数组也没有实现深拷贝
let obj = {
arr: [0, 1, 2],
};
const ret = deepCopy(obj);
console.info(ret.arr === obj.arr);
仍然返回true
/**
* @description 深度克隆
* @param data 克隆目标
* @param hash 标记目标是否被克隆过
*/
const cloneDeep = function <T>(data: T, hash: WeakMap<object, object> = new WeakMap()): T {
// 特例判断, 另外函数也不需要被克隆
if (data === null || typeof data !== 'object' || typeof data === 'function') {
return data;
}
// 数组
if (Array.isArray(data)) {
const res: any[] = [];
for (let index in data) {
res[index] = cloneDeep(data[index], hash);
}
return res as T;
}
// 对象
else {
if (hash.get(data)) { //如果目标已经被克隆过了,返回旧值
return hash.get(data) as T;
}
const res: any = Object.create(Object.getPrototypeOf(data)) //保留对象原型
hash.set(data, res);
for (let key of Reflect.ownKeys(data)) { //ownKeys 可以获取到对象的普通属性,不可枚举属性, symbol属性
const item = Reflect.get(data, key);
Reflect.set(res, key, cloneDeep(item, hash));
}
return res as T;
}
}
export default cloneDeep
// 假设只考虑基本类型, symbol, 数组和对象以及对象循环引用
deepClone.cache = deepClone.cache || new Map();
function deepClone(data) {
const type = Object.prototype.toString.call(data);
let result;
switch (type) {
case "[object Array]":
result = [];
for (let item of data) {
if (typeof item === "object") {
result.push(deepClone(item));
} else {
result.push(item);
}
}
break;
case "[object Object]":
if (deepClone.cache.get(data)) {
return deepClone.cache.get(data);
}
result = {};
deepClone.cache.set(data, result);
// copy symbols
const symbols = Object.getOwnPropertySymbols(data);
for (let symbol of symbols) {
result[symbol] = data[symbol];
}
for (let key in data) {
if (typeof data[key] === "object") {
result[key] = deepClone(data[key]);
} else {
result[key] = data[key];
}
}
break;
default:
}
return result;
}
const array = [
{ name: "Nancy", age: 20 },
{ name: "Jack", age: 21 },
];
const object = {
name: "string",
age: "number",
};
const loop = {
id: 1,
name: "id-1",
children: [],
};
loop.children.push(loop);
const obj = {
array,
object,
type: "test deepClone",
priority: 1,
[Symbol("symbol")]: "test symbol",
loop,
};
const deepClonedObj = deepClone(obj);
console.log("obj", obj);
console.log("after deep clone: ", deepClonedObj);
array[0].name = "Alice";
object.age = "double";
loop.id = 2;
obj.priority = 2;
console.log("after change obj: ", obj, deepClonedObj);
不知道有没有漏的。。