-
Notifications
You must be signed in to change notification settings - Fork 3.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
第 95 题:模拟实现一个深拷贝,并考虑对象相互引用以及 Symbol 拷贝的情况 #148
Comments
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优雅强一些 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)
} 无论哪种方法,它们都有一个共性:失去了继承关系,所以剩下的需要我们手动补上去了,故有 |
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]); |
有两个问题:
|
数组的确是我忘了写return了。然后拷贝函数这种操作平时真不会有人做。如果实在是要拷贝,除了简单的function.toString和正则匹配外,还要考虑箭头函数、参数默认值、换行、this、函数名字 |
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;
} 函数拷贝的情况太复杂了,所以就直接用了 |
写个不用递归用循环的方式实现的版本吧 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(手动狗头) |
var a=[]; |
野路子缺点:无法拷贝函数 、Symbol const deepClone = function (obj) {
var str = JSON.stringify(obj);
return JSON.parse(str);
} |
Symbol 不是独一无二的吗?还能拷贝? |
函数为什么要拷贝?函数不是用来复用的吗? |
|
分享一个来自 Vuex 的 deepCopy 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
} |
为什么没人用 |
@lhyt 的
在这种情况下有问题 let ooo = {
a: {}
};
ooo.a.c = ooo;
let eee = deepCopy(ooo)
console.log(eee.a.c === eee);
console.log(eee.a.c === ooo); 感觉环状数据之前这个里面 #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 |
不考虑正则、函数等奇怪类型的拷贝,满足大多数深度拷贝需求 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
} |
记录一下简单做的
|
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) { |
流下了没有技术的眼泪... |
少了个 const find = (cache, fn) => cache.filter(fn)[0]
|
|
参考上面几位大佬的答案整理了一下
|
|
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) |
|
function deepclone(obj,hash = new WeakMap()){ |
来源于拉勾JavaScript核心原理精讲课程。
|
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 |
|
使用深度优先遍历、WeakSet来实现。 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等)
|
|
为什么要使用weakmap?求解 |
https://juejin.cn/post/6990274836278804517#heading-7 |
function copyObj(obj) { |
|
对象里的数组也没有实现深拷贝
仍然返回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); |
No description provided.
The text was updated successfully, but these errors were encountered: