学习链接
深浅拷贝
浅拷贝
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 trueconsole.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);}}
