学习链接
深浅拷贝
浅拷贝
function shallowClone(source) {
const target = {};
for (const prop of Object.keys(source)) {
target.prop = source.prop;
}
return target;
}
深拷贝
递归
function deepClone(source) {
const target = {};
for (const property of Object.keys(source)) {
if (typeof source[property] === 'object' && source[property] !== null) {
target[property] = deepClone(source[property]);
} else {
target[property] = source[property];
}
}
return target;
}
递归👉迭代
function deepClone(source) {
const target = {};
let stack = [{ target, source }];
while (stack.length) {
const { target, source } = stack.pop();
for (const property of Object.keys(source)) {
// 非 null 对象
if (typeof source[property] === 'object' && source[property] !== null) {
target[property] = {};
stack.push({ target: target[property], source: source[property] });
continue;
}
target[property] = source[property];
}
}
return target;
}
考虑循环引用
function deepClone(source) {
const target = {};
const map = new Map();
const stack = [{ source, target }];
map.set(source, target);
while (stack.length) {
const { source, target } = stack.pop();
for (const property of Object.keys(source)) {
// 循环引用
if (map.has(source[property])) {
target[property] = map.get(source[property]);
continue;
}
// 非 null 对象
if (typeof source[property] === 'object' && source[property] !== null) {
target[property] = {};
stack.push({ source: source[property], target: target[property] });
map.set(source[property], target[property]);
continue;
}
target[property] = source[property];
}
}
return target;
}
考虑原型
function deepClone(source) {
const target = Object.create(
Object.getPrototypeOf(source)
);
// const target = {};
// Object.setPrototypeOf(
// target,
// Object.getPrototypeOf(source)
// );
const map = new Map();
const stack = [{ source, target }];
map.set(source, target);
while (stack.length) {
let { source, target } = stack.pop();
for (const property of Object.keys(source)) {
// 原始类型
if (typeof source[property] !== 'object' || source[property] === null) {
target[property] = source[property];
continue;
}
// 循环引用
if (map.has(source[property])) {
target[property] = map.get(source[property]);
continue;
}
// 处理原型
target[property] = Object.create(
Object.getPrototypeOf(source[property])
);
// target[property] = {};
// Object.setPrototypeOf(
// target[property],
// Object.getPrototypeOf(source[property])
// );
stack.push({ source: source[property], target: target[property] });
map.set(source[property], target[property]);
}
}
return target;
}
考虑 descriptor
function deepClone(source) {
const target = Object.create(
Object.getPrototypeOf(source)
);
const map = new Map();
let stack = [{ source, target }];
map.set(source, target);
while (stack.length) {
let { source, target } = stack.pop();
for (const property of Object.getOwnPropertyNames(source)) {
const descriptor = Object.getOwnPropertyDescriptor(source, property);
// getter setter 属性
if (!descriptor.hasOwnProperty('value')) {
Object.defineProperty(target, property, descriptor);
continue;
}
// 原始类型
if (typeof source[property] !== 'object' || source[property] === null) {
descriptor.value = source[property];
Object.defineProperty(target, property, descriptor);
continue;
}
// 循环引用
if (map.has(source[property])) {
descriptor.value = map.get(source[property]);
Object.defineProperty(target, property, descriptor);
continue;
}
// 处理原型
descriptor.value = Object.create(
Object.getPrototypeOf(source[property])
);
stack.push({ source: source[property], target: descriptor.value });
map.set(source[property], descriptor.value);
// 添加属性
Object.defineProperty(target, property, descriptor);
}
}
return target;
}
考虑 freeze,seal,preventExtensions
补充知识:
const obj = { a: 1 }
// Object.preventExtensions(obj) // false false false
// Object.seal(obj) // false true false
// Object.freeze(obj) // flase true true
console.log('Object.isExtensible(obj): ', Object.isExtensible(obj));
console.log('Object.isSealed(obj): ', Object.isSealed(obj));
console.log('Object.isFrozen(obj): ', Object.isFrozen(obj));
function deepClone(source) {
const target = Object.create(
Object.getPrototypeOf(source)
);
const map = new Map();
let stack = [{ source, target }];
map.set(source, target);
while (stack.length) {
let { source, target } = stack.pop();
for (const property of Object.getOwnPropertyNames(source)) {
const descriptor = Object.getOwnPropertyDescriptor(source, property);
// getter setter 属性
if (!descriptor.hasOwnProperty('value')) {
Object.defineProperty(target, property, descriptor);
continue;
}
// 原始类型
if (typeof source[property] !== 'object' || source[property] === null) {
descriptor.value = source[property];
Object.defineProperty(target, property, descriptor);
continue;
}
// 循环引用
if (map.has(source[property])) {
descriptor.value = map.get(source[property]);
Object.defineProperty(target, property, descriptor);
continue;
}
// 处理原型
descriptor.value = Object.create(
Object.getPrototypeOf(source[property])
);
stack.push({ source: source[property], target: descriptor.value });
map.set(source[property], descriptor.value);
// 添加属性
Object.defineProperty(target, property, descriptor);
// '冻结' 对象
if (Object.isFrozen(source)) {
Object.freeze(target);
} else if (Object.isSealed(source)) {
Object.seal(target);
} else if (!Object.isExtensible(source)) {
Object.preventExtensions(target);
}
}
}
return target;
}
考虑更多对象类型
function deepClone(source) {
if (typeof source !== 'object') return source;
// ......
const target = createObjectFromSameClassObject(source);
// ......
while (stack.length) {
let { source, target } = stack.pop();
for (const property of Object.getOwnPropertyNames(source)) {
// ......
// 处理原型
descriptor.value = createObjectFromSameClassObject(source[property]);
// ......
}
// ......
}
return target;
}
function createObjectFromSameClassObject(source) {
let target;
let type = typeof source;
let toStringResult = Object.prototype.toString.call(source);
let prototype = Object.getPrototypeOf(source);
if (prototype === Array.prototype && toStringResult === '[object Array]') {
target = source.slice();
} else if (prototype === String.prototype && toStringResult === '[object String]') {
target = new String(source.valueOf());
} else {
target = Object.create(
Object.getPrototypeOf(source)
);
}
return target;
}
改为易于拓展的版本
function deepClone(source) {
// ......
}
const classes = [
{
typeCheck(source, type, toStringResult, prototype) {
return prototype === Array.prototype && toStringResult === '[object Array]';
},
createObj(source) {
return source.slice();
// return []; // 觉得这样也可以
}
},
{
typeCheck(source, type, toStringResult, prototype) {
return prototype === String.prototype && toStringResult === '[object String]';
},
createObj(source) {
return new String(source.valueOf());
}
}
];
function createObjectFromSameClassObject(source) {
let type = typeof source;
let toStringResult = Object.prototype.toString.call(source);
let prototype = Object.getPrototypeOf(source);
// 处理特殊类型
for (const { typeCheck, createObj } of classes) {
if (typeCheck(source, type, toStringResult, prototype)) {
return createObj(source);
}
}
return Object.create(
Object.getPrototypeOf(source)
);
}
举例:处理 Map 类型
classes.push({
checkType(source, type, toStringResult, prototype) {
return prototype === Map.prototype && toStringResult === '[object Map]';
},
createObj(source) {
const map = new Map();
for (const [key, value] of source) {
// (键值都进行拷贝处理 这是因为map的键值都可为任意类型)
map.set(deepClone(key), deepClone(value));
}
return map;
}
});
测试
it('clone a object', () => {
const obj = { a: 1 };
const cloneObj = deepClone(obj);
return (
cloneObj !== obj &&
JSON.stringify(obj) === JSON.stringify(cloneObj)
);
});
it('clone a object with ciruclar reference', () => {
const obj = { a: 1, b: { x: 1 } };
obj.b.c = obj.b;
const cloneObj = deepClone(obj);
return (
cloneObj !== obj &&
cloneObj.b !== obj.b &&
cloneObj.b.c === cloneObj.b
);
});
it('clone a object with prototype', () => {
function Cls() {
this.a = 1;
}
Cls.prototype.b = 2;
const obj = new Cls();
const cloneObj = deepClone(obj);
return (
cloneObj.a === obj.a &&
cloneObj.b === obj.b &&
!cloneObj.hasOwnProperty('b')
);
});
it('clone a object with property with enumberable: false', () => {
const obj = {};
Object.defineProperty(obj, 'a', {
value: 1,
writable: true,
enumerable: false,
configurable: true
});
const cloneObj = deepClone(obj);
for (const p in cloneObj) {
return false;
}
return true;
});
it('clone a frozen object', () => {
const obj = { a: 1, b: { x: 2 } };
Object.freeze(obj);
const cloneObj = deepClone(obj);
return Object.isFrozen(cloneObj);
});
it('clone a sealed object', () => {
const obj = { a: 1, b: { x: 2 } };
Object.seal(obj);
const cloneObj = deepClone(obj);
return Object.isSealed(cloneObj);
});
it('clone a unextensible object', () => {
const obj = { a: 1, b: { x: 2 } };
Object.preventExtensions(obj);
const cloneObj = deepClone(obj);
return !Object.isExtensible(cloneObj);
});
it('clone a Array', () => {
const arr = ['a', 'b', 'c'];
const cloneArr = deepClone(arr);
return (
JSON.stringify(arr) === JSON.stringify(cloneArr) &&
arr !== cloneArr &&
Array.isArray(cloneArr)
);
});
it('clone a String Object', () => {
const str = new String('abc');
const cloneStr = deepClone(str);
return (
JSON.stringify(str) === JSON.stringify(cloneStr) &&
str !== cloneStr &&
cloneStr.valueOf() === 'abc'
);
});
function it(str, fun) {
if (fun()) {
console.log(str);
} else {
console.log(false);
}
}