学习链接

鉴定一下网络热门面试题:对象深克隆(上)

鉴定一下网络热门面试题:对象深克隆(下)

MDN:structuredClone

MDN:Object.assign

深浅拷贝

浅拷贝

  1. function shallowClone(source) {
  2. const target = {};
  3. for (const prop of Object.keys(source)) {
  4. target.prop = source.prop;
  5. }
  6. return target;
  7. }

深拷贝

递归

  1. function deepClone(source) {
  2. const target = {};
  3. for (const property of Object.keys(source)) {
  4. if (typeof source[property] === 'object' && source[property] !== null) {
  5. target[property] = deepClone(source[property]);
  6. } else {
  7. target[property] = source[property];
  8. }
  9. }
  10. return target;
  11. }

递归👉迭代

  1. function deepClone(source) {
  2. const target = {};
  3. let stack = [{ target, source }];
  4. while (stack.length) {
  5. const { target, source } = stack.pop();
  6. for (const property of Object.keys(source)) {
  7. // 非 null 对象
  8. if (typeof source[property] === 'object' && source[property] !== null) {
  9. target[property] = {};
  10. stack.push({ target: target[property], source: source[property] });
  11. continue;
  12. }
  13. target[property] = source[property];
  14. }
  15. }
  16. return target;
  17. }

考虑循环引用

  1. function deepClone(source) {
  2. const target = {};
  3. const map = new Map();
  4. const stack = [{ source, target }];
  5. map.set(source, target);
  6. while (stack.length) {
  7. const { source, target } = stack.pop();
  8. for (const property of Object.keys(source)) {
  9. // 循环引用
  10. if (map.has(source[property])) {
  11. target[property] = map.get(source[property]);
  12. continue;
  13. }
  14. // 非 null 对象
  15. if (typeof source[property] === 'object' && source[property] !== null) {
  16. target[property] = {};
  17. stack.push({ source: source[property], target: target[property] });
  18. map.set(source[property], target[property]);
  19. continue;
  20. }
  21. target[property] = source[property];
  22. }
  23. }
  24. return target;
  25. }

考虑原型

  1. function deepClone(source) {
  2. const target = Object.create(
  3. Object.getPrototypeOf(source)
  4. );
  5. // const target = {};
  6. // Object.setPrototypeOf(
  7. // target,
  8. // Object.getPrototypeOf(source)
  9. // );
  10. const map = new Map();
  11. const stack = [{ source, target }];
  12. map.set(source, target);
  13. while (stack.length) {
  14. let { source, target } = stack.pop();
  15. for (const property of Object.keys(source)) {
  16. // 原始类型
  17. if (typeof source[property] !== 'object' || source[property] === null) {
  18. target[property] = source[property];
  19. continue;
  20. }
  21. // 循环引用
  22. if (map.has(source[property])) {
  23. target[property] = map.get(source[property]);
  24. continue;
  25. }
  26. // 处理原型
  27. target[property] = Object.create(
  28. Object.getPrototypeOf(source[property])
  29. );
  30. // target[property] = {};
  31. // Object.setPrototypeOf(
  32. // target[property],
  33. // Object.getPrototypeOf(source[property])
  34. // );
  35. stack.push({ source: source[property], target: target[property] });
  36. map.set(source[property], target[property]);
  37. }
  38. }
  39. return target;
  40. }

考虑 descriptor

  1. function deepClone(source) {
  2. const target = Object.create(
  3. Object.getPrototypeOf(source)
  4. );
  5. const map = new Map();
  6. let stack = [{ source, target }];
  7. map.set(source, target);
  8. while (stack.length) {
  9. let { source, target } = stack.pop();
  10. for (const property of Object.getOwnPropertyNames(source)) {
  11. const descriptor = Object.getOwnPropertyDescriptor(source, property);
  12. // getter setter 属性
  13. if (!descriptor.hasOwnProperty('value')) {
  14. Object.defineProperty(target, property, descriptor);
  15. continue;
  16. }
  17. // 原始类型
  18. if (typeof source[property] !== 'object' || source[property] === null) {
  19. descriptor.value = source[property];
  20. Object.defineProperty(target, property, descriptor);
  21. continue;
  22. }
  23. // 循环引用
  24. if (map.has(source[property])) {
  25. descriptor.value = map.get(source[property]);
  26. Object.defineProperty(target, property, descriptor);
  27. continue;
  28. }
  29. // 处理原型
  30. descriptor.value = Object.create(
  31. Object.getPrototypeOf(source[property])
  32. );
  33. stack.push({ source: source[property], target: descriptor.value });
  34. map.set(source[property], descriptor.value);
  35. // 添加属性
  36. Object.defineProperty(target, property, descriptor);
  37. }
  38. }
  39. return target;
  40. }

考虑 freeze,seal,preventExtensions

补充知识:

深浅拷贝 - 图1

  1. const obj = { a: 1 }
  2. // Object.preventExtensions(obj) // false false false
  3. // Object.seal(obj) // false true false
  4. // Object.freeze(obj) // flase true true
  5. console.log('Object.isExtensible(obj): ', Object.isExtensible(obj));
  6. console.log('Object.isSealed(obj): ', Object.isSealed(obj));
  7. console.log('Object.isFrozen(obj): ', Object.isFrozen(obj));
  1. function deepClone(source) {
  2. const target = Object.create(
  3. Object.getPrototypeOf(source)
  4. );
  5. const map = new Map();
  6. let stack = [{ source, target }];
  7. map.set(source, target);
  8. while (stack.length) {
  9. let { source, target } = stack.pop();
  10. for (const property of Object.getOwnPropertyNames(source)) {
  11. const descriptor = Object.getOwnPropertyDescriptor(source, property);
  12. // getter setter 属性
  13. if (!descriptor.hasOwnProperty('value')) {
  14. Object.defineProperty(target, property, descriptor);
  15. continue;
  16. }
  17. // 原始类型
  18. if (typeof source[property] !== 'object' || source[property] === null) {
  19. descriptor.value = source[property];
  20. Object.defineProperty(target, property, descriptor);
  21. continue;
  22. }
  23. // 循环引用
  24. if (map.has(source[property])) {
  25. descriptor.value = map.get(source[property]);
  26. Object.defineProperty(target, property, descriptor);
  27. continue;
  28. }
  29. // 处理原型
  30. descriptor.value = Object.create(
  31. Object.getPrototypeOf(source[property])
  32. );
  33. stack.push({ source: source[property], target: descriptor.value });
  34. map.set(source[property], descriptor.value);
  35. // 添加属性
  36. Object.defineProperty(target, property, descriptor);
  37. // '冻结' 对象
  38. if (Object.isFrozen(source)) {
  39. Object.freeze(target);
  40. } else if (Object.isSealed(source)) {
  41. Object.seal(target);
  42. } else if (!Object.isExtensible(source)) {
  43. Object.preventExtensions(target);
  44. }
  45. }
  46. }
  47. return target;
  48. }

考虑更多对象类型

  1. function deepClone(source) {
  2. if (typeof source !== 'object') return source;
  3. // ......
  4. const target = createObjectFromSameClassObject(source);
  5. // ......
  6. while (stack.length) {
  7. let { source, target } = stack.pop();
  8. for (const property of Object.getOwnPropertyNames(source)) {
  9. // ......
  10. // 处理原型
  11. descriptor.value = createObjectFromSameClassObject(source[property]);
  12. // ......
  13. }
  14. // ......
  15. }
  16. return target;
  17. }
  18. function createObjectFromSameClassObject(source) {
  19. let target;
  20. let type = typeof source;
  21. let toStringResult = Object.prototype.toString.call(source);
  22. let prototype = Object.getPrototypeOf(source);
  23. if (prototype === Array.prototype && toStringResult === '[object Array]') {
  24. target = source.slice();
  25. } else if (prototype === String.prototype && toStringResult === '[object String]') {
  26. target = new String(source.valueOf());
  27. } else {
  28. target = Object.create(
  29. Object.getPrototypeOf(source)
  30. );
  31. }
  32. return target;
  33. }

改为易于拓展的版本

  1. function deepClone(source) {
  2. // ......
  3. }
  4. const classes = [
  5. {
  6. typeCheck(source, type, toStringResult, prototype) {
  7. return prototype === Array.prototype && toStringResult === '[object Array]';
  8. },
  9. createObj(source) {
  10. return source.slice();
  11. // return []; // 觉得这样也可以
  12. }
  13. },
  14. {
  15. typeCheck(source, type, toStringResult, prototype) {
  16. return prototype === String.prototype && toStringResult === '[object String]';
  17. },
  18. createObj(source) {
  19. return new String(source.valueOf());
  20. }
  21. }
  22. ];
  23. function createObjectFromSameClassObject(source) {
  24. let type = typeof source;
  25. let toStringResult = Object.prototype.toString.call(source);
  26. let prototype = Object.getPrototypeOf(source);
  27. // 处理特殊类型
  28. for (const { typeCheck, createObj } of classes) {
  29. if (typeCheck(source, type, toStringResult, prototype)) {
  30. return createObj(source);
  31. }
  32. }
  33. return Object.create(
  34. Object.getPrototypeOf(source)
  35. );
  36. }

举例:处理 Map 类型

  1. classes.push({
  2. checkType(source, type, toStringResult, prototype) {
  3. return prototype === Map.prototype && toStringResult === '[object Map]';
  4. },
  5. createObj(source) {
  6. const map = new Map();
  7. for (const [key, value] of source) {
  8. // (键值都进行拷贝处理 这是因为map的键值都可为任意类型)
  9. map.set(deepClone(key), deepClone(value));
  10. }
  11. return map;
  12. }
  13. });

测试

  1. it('clone a object', () => {
  2. const obj = { a: 1 };
  3. const cloneObj = deepClone(obj);
  4. return (
  5. cloneObj !== obj &&
  6. JSON.stringify(obj) === JSON.stringify(cloneObj)
  7. );
  8. });
  9. it('clone a object with ciruclar reference', () => {
  10. const obj = { a: 1, b: { x: 1 } };
  11. obj.b.c = obj.b;
  12. const cloneObj = deepClone(obj);
  13. return (
  14. cloneObj !== obj &&
  15. cloneObj.b !== obj.b &&
  16. cloneObj.b.c === cloneObj.b
  17. );
  18. });
  19. it('clone a object with prototype', () => {
  20. function Cls() {
  21. this.a = 1;
  22. }
  23. Cls.prototype.b = 2;
  24. const obj = new Cls();
  25. const cloneObj = deepClone(obj);
  26. return (
  27. cloneObj.a === obj.a &&
  28. cloneObj.b === obj.b &&
  29. !cloneObj.hasOwnProperty('b')
  30. );
  31. });
  32. it('clone a object with property with enumberable: false', () => {
  33. const obj = {};
  34. Object.defineProperty(obj, 'a', {
  35. value: 1,
  36. writable: true,
  37. enumerable: false,
  38. configurable: true
  39. });
  40. const cloneObj = deepClone(obj);
  41. for (const p in cloneObj) {
  42. return false;
  43. }
  44. return true;
  45. });
  46. it('clone a frozen object', () => {
  47. const obj = { a: 1, b: { x: 2 } };
  48. Object.freeze(obj);
  49. const cloneObj = deepClone(obj);
  50. return Object.isFrozen(cloneObj);
  51. });
  52. it('clone a sealed object', () => {
  53. const obj = { a: 1, b: { x: 2 } };
  54. Object.seal(obj);
  55. const cloneObj = deepClone(obj);
  56. return Object.isSealed(cloneObj);
  57. });
  58. it('clone a unextensible object', () => {
  59. const obj = { a: 1, b: { x: 2 } };
  60. Object.preventExtensions(obj);
  61. const cloneObj = deepClone(obj);
  62. return !Object.isExtensible(cloneObj);
  63. });
  64. it('clone a Array', () => {
  65. const arr = ['a', 'b', 'c'];
  66. const cloneArr = deepClone(arr);
  67. return (
  68. JSON.stringify(arr) === JSON.stringify(cloneArr) &&
  69. arr !== cloneArr &&
  70. Array.isArray(cloneArr)
  71. );
  72. });
  73. it('clone a String Object', () => {
  74. const str = new String('abc');
  75. const cloneStr = deepClone(str);
  76. return (
  77. JSON.stringify(str) === JSON.stringify(cloneStr) &&
  78. str !== cloneStr &&
  79. cloneStr.valueOf() === 'abc'
  80. );
  81. });
  82. function it(str, fun) {
  83. if (fun()) {
  84. console.log(str);
  85. } else {
  86. console.log(false);
  87. }
  88. }