Daily-Interview-Question
Daily-Interview-Question copied to clipboard
第 95 题:模拟实现一个深拷贝,并考虑对象相互引用以及 Symbol 拷贝的情况
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 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)
的操作
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、函数名字
- 如果obj是null, 或者不是函数也不是object(即为包括Symbol在内的基本类型)则直接返回obj;
- 如果obj是Date或RegExp就返回对应的新实例;
- 在map中查找,找到则返回;
- 以上都不是,则通过
new obj.constructor()
或eval(obj.toString())
创建一个新实例temp,并保存进map,通过Object.getOwnPropertyNames
和Object.getOwnPropertySymbols
遍历obj的所有属性名,递归调用deepClone完成temp上所有属性的声明和赋值,最后返回temp
function 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)