• JS高级 之 深拷贝 && 浅拷贝


    目录

    一、浅拷贝的实现

    1. Object.assgin

    2. 展开运算符

    3. Array.prototype.concat

    4. Array.prototype.slice

    二、深拷贝的实现

    0. 判断一个值是否是对象类型

    1. JSON实现

    2. 手写深拷贝函数

    01 - 基本手写

    02 - 优化 => 数组

    03 - 优化 => 函数、Set、Map、Symbol

    04 - 优化 => 循环引用 

    04 - 最终方案 


    一、浅拷贝的实现

    1. Object.assgin

    1. const obj = {
    2. name: 'star',
    3. age: 18,
    4. friend: {
    5. name: 'coder'
    6. }
    7. };
    8. // 浅拷贝
    9. const info = Object.assign({}, obj);
    10. console.log(info); // { name: 'star', age: 18, friend: { name: 'coder' } }
    11. // 修改引用类型数据,同时会更改
    12. obj.friend.name = '123';
    13. console.log(info); // { name: 'star', age: 18, friend: { name: '123' } }

    2. 展开运算符

    1. const obj = {
    2. name: 'star',
    3. age: 18,
    4. friend: {
    5. name: 'coder'
    6. }
    7. };
    8. // 浅拷贝
    9. const info = { ...obj };
    10. console.log(info); // { name: 'star', age: 18, friend: { name: 'coder' } }
    11. // 修改引用类型数据,同时会更改
    12. obj.friend.name = '123';
    13. console.log(info); // { name: 'star', age: 18, friend: { name: '123' } }

    3. Array.prototype.concat

    1. const arr = [1, { name: 'coder' }, '4'];
    2. // 浅拷贝
    3. const info = [].concat.call(arr, [{ age: 18 }]);
    4. // const info = Array.prototype.concat.call(arr, [{ age: 18 }]);
    5. console.log(info); //[ 1, { name: 'coder' }, '4', { age: 18 } ]
    6. // 修改引用类型数据,同时会更改
    7. arr[1].name = '123';
    8. console.log(info); // [ 1, { name: '123' }, '4', { age: 18 } ]

    4. Array.prototype.slice

    1. const arr = [1, 2, { name: 'coder' }, '4'];
    2. // 浅拷贝
    3. const info = [].slice.call(arr);
    4. // const info = [].slice.call(arr, 0);
    5. // const info = [].slice.call(arr, 0, 4);
    6. // const info = Array.prototype.slice.call(arr);
    7. console.log(info); // [ 1, 2, { name: 'coder' }, '4' ]
    8. // 修改引用类型数据,同时会更改
    9. arr[2].name = '123';
    10. console.log(info); // [ 1, 2, { name: '123' }, '4' ]

    二、深拷贝的实现

    0. 判断一个值是否是对象类型

    1. // 判断一个标识符是否是对象类型
    2. function isObject(value) {
    3. const valueType = typeof value;
    4. return value !== null && (valueType === 'object' || valueType === 'function');
    5. }

    1. JSON实现

    1. const obj = {
    2. name: 'star',
    3. age: 18,
    4. friend: {
    5. name: 'coder',
    6. arr: [1, 2, 3, 4]
    7. }
    8. };
    9. // 深拷贝
    10. const info = JSON.parse(JSON.stringify(obj));
    11. console.log(info);
    12. // 修改原来属性,不会影响新创建的对象
    13. obj.friend.name = '123';
    14. console.log(info);

    缺点 : 

            1. function 不会被转化,直接忽略掉了

            2. symbal 不会被转化,直接忽略掉了

            3. 存在undefined、NaN 之类情况会转化错误

            4. 存在循环引用的情况直接报错

    2. 手写深拷贝函数

    01 - 基本手写

    1. function isObject(value) {
    2. const valueType = typeof value;
    3. return value !== null && (valueType === 'object' || valueType === 'function');
    4. }
    5. function deepCopy(originValue) {
    6. // 1. 如果不是引用类型,直接返回
    7. if (!isObject(originValue)) {
    8. return originValue;
    9. }
    10. // 2. 创建新对象
    11. const newObj = {};
    12. for (const key in originValue) {
    13. // 3. 赋值的时候,进行递归
    14. newObj[key] = deepCopy(originValue[key]);
    15. }
    16. // 4. 返回新对象
    17. return newObj;
    18. }
    19. const obj = {
    20. name: 'star',
    21. age: 18,
    22. friend: {
    23. name: 'coder'
    24. }
    25. };
    26. // 深拷贝
    27. const info = deepCopy(obj);
    28. console.log(info);
    29. // 修改原来属性,不会影响新创建的对象
    30. obj.friend.name = '123';
    31. console.log(info);

    02 - 优化 => 数组

    1. function isObject(value) {
    2. const valueType = typeof value;
    3. return value !== null && (valueType === 'object' || valueType === 'function');
    4. }
    5. function deepCopy(originValue) {
    6. // 1. 如果不是引用类型,直接返回
    7. if (!isObject(originValue)) {
    8. return originValue;
    9. }
    10. // 2. 创建新对象 | 数组
    11. const newObj = Array.isArray(originValue) ? [] : {};
    12. for (const key in originValue) {
    13. // 3. 赋值的时候,进行递归
    14. newObj[key] = deepCopy(originValue[key]);
    15. }
    16. // 4. 返回新对象
    17. return newObj;
    18. }
    19. const obj = {
    20. name: 'star',
    21. age: 18,
    22. friend: {
    23. name: 'coder',
    24. info: [1, 2, 3, 4, 5]
    25. }
    26. };
    27. // 深拷贝
    28. const info = deepCopy(obj);
    29. console.log(info);
    30. obj.age = 20;
    31. console.log(info);

    03 - 优化 => 函数、Set、Map、Symbol

    1. function isObject(value) {
    2. const valueType = typeof value;
    3. return value !== null && (valueType === 'object' || valueType === 'function');
    4. }
    5. // 深拷贝函数
    6. function deepCopy(originValue) {
    7. /**
    8. * 值是 symbol数据类型
    9. * 需写在基本数据类型之前,否则会被返回错误
    10. */
    11. if (typeof originValue === 'symbol') {
    12. return Symbol(originValue.description);
    13. }
    14. /**
    15. * 基本数据类型,直接返回
    16. */
    17. if (!isObject(originValue)) {
    18. return originValue;
    19. }
    20. /**
    21. * 函数类型
    22. * 不需要拷贝,直接返回
    23. */
    24. if (typeof originValue === 'function') {
    25. return originValue;
    26. }
    27. /**
    28. * set类型
    29. * 因为set类型不能被for...in,所以单独处理
    30. */
    31. if (originValue instanceof Set) {
    32. const newSet = new Set();
    33. for (const item of originValue) {
    34. newSet.add(deepCopy(item));
    35. }
    36. return newSet;
    37. }
    38. /**
    39. * map类型
    40. * 因为set类型不能被for...in,所以单独处理
    41. */
    42. if (originValue instanceof Map) {
    43. const newMap = new Map();
    44. for (const [key, value] of originValue) {
    45. newMap.set(deepCopy(key), deepCopy(value));
    46. }
    47. return newMap;
    48. }
    49. /**
    50. * 对象,数组类型
    51. */
    52. const newObj = Array.isArray(originValue) ? [] : {};
    53. // one - 遍历普通的key
    54. for (const key in originValue) {
    55. newObj[key] = deepCopy(originValue[key]);
    56. }
    57. // two - 遍历Symbol的key
    58. const symbolKeys = Object.getOwnPropertySymbols(originValue);
    59. for (const symbolKey of symbolKeys) {
    60. newObj[Symbol(symbolKey.description)] = deepCopy(originValue[symbolKey]);
    61. }
    62. // 4. 返回新对象 | 数组
    63. return newObj;
    64. }
    65. const symbolKey = Symbol('key是Symbol');
    66. const obj = {
    67. name: 'star',
    68. age: 18,
    69. friend: {
    70. name: 'coder',
    71. info: [1, 2, 3, 4, 5]
    72. },
    73. play() {
    74. console.log('playing!');
    75. },
    76. set: new Set([1, 22, 3]),
    77. map: new Map([
    78. [{ name: 5 }, 5],
    79. [6, { name: 6 }]
    80. ]),
    81. [symbolKey]: 'key是Symbol',
    82. symbolValue: Symbol('value是Symbol')
    83. };
    84. // 深拷贝
    85. const info = deepCopy(obj);
    86. console.log(info);

    04 - 优化 => 循环引用 

    1. function isObject(value) {
    2. const valueType = typeof value;
    3. return value !== null && (valueType === 'object' || valueType === 'function');
    4. }
    5. /**
    6. * 循环引用解决方案 :
    7. * 拷贝过的对象,不需要再次拷贝,直接返回即可
    8. * 1. 使用 WeakMap 进行弱引用,用来判断是否拷贝过
    9. * 2. 使用同一个 WeakMap
    10. */
    11. function deepCopy(originValue, map = new WeakMap()) {
    12. if (typeof originValue === 'symbol') {
    13. return Symbol(originValue.description);
    14. }
    15. if (!isObject(originValue)) {
    16. return originValue;
    17. }
    18. if (typeof originValue === 'function') {
    19. return originValue;
    20. }
    21. if (originValue instanceof Set) {
    22. const newSet = new Set();
    23. for (const item of originValue) {
    24. // 为了使得使用的 map 都为同一个,调用时把map传入,使得map 都为最开始创建的那一个
    25. newSet.add(deepCopy(item, map));
    26. }
    27. return newSet;
    28. }
    29. if (originValue instanceof Map) {
    30. const newMap = new Map();
    31. for (const [key, value] of originValue) {
    32. // 为了使得使用的 map 都为同一个,调用时把map传入,使得map 都为最开始创建的那一个
    33. newMap.set(deepCopy(key, map), deepCopy(value, map));
    34. }
    35. return newMap;
    36. }
    37. /**
    38. * 每次创建对象前,判断map中是否已经拥有了 => 即是否已经拷贝过了
    39. * 如果有,直接返回以前创建的对象
    40. */
    41. if (map.get(originValue)) {
    42. return map.get(originValue);
    43. }
    44. const newObj = Array.isArray(originValue) ? [] : {};
    45. /**
    46. * 1. 每次创建新对象后,推入 WeakMap 中,用来保存
    47. * 2. 当前的对象的地址作为 key ,值为新创建出来的对象
    48. */
    49. map.set(originValue, newObj);
    50. for (const key in originValue) {
    51. // 为了使得使用的 map 都为同一个,调用时把map传入,使得map 都为最开始创建的那一个
    52. newObj[key] = deepCopy(originValue[key], map);
    53. }
    54. const symbolKeys = Object.getOwnPropertySymbols(originValue);
    55. for (const symbolKey of symbolKeys) {
    56. // 为了使得使用的 map 都为同一个,调用时把map传入,使得map 都为最开始创建的那一个
    57. newObj[Symbol(symbolKey.description)] = deepCopy(originValue[symbolKey], map);
    58. }
    59. return newObj;
    60. }
    61. const symbolKey = Symbol('key是Symbol');
    62. const obj = {
    63. name: 'star',
    64. age: 18,
    65. friend: {
    66. name: 'coder',
    67. info: [1, 2, 3, 4, 5]
    68. }
    69. };
    70. // 自己引用自己
    71. obj.obj = obj;
    72. // 深拷贝
    73. const info = deepCopy(obj);
    74. console.log(info);

    04 - 最终方案 

    1. // 判断一个值是否是对象类型
    2. function isObject(value) {
    3. const valueType = typeof value;
    4. return value !== null && (valueType === 'object' || valueType === 'function');
    5. }
    6. // 深拷贝函数
    7. function deepCopy(originValue, map = new WeakMap()) {
    8. if (typeof originValue === 'symbol') {
    9. return Symbol(originValue.description);
    10. }
    11. if (!isObject(originValue)) {
    12. return originValue;
    13. }
    14. if (typeof originValue === 'function') {
    15. return originValue;
    16. }
    17. if (originValue instanceof Set) {
    18. const newSet = new Set();
    19. for (const item of originValue) {
    20. newSet.add(deepCopy(item, map));
    21. }
    22. return newSet;
    23. }
    24. if (originValue instanceof Map) {
    25. const newMap = new Map();
    26. for (const [key, value] of originValue) {
    27. newMap.set(deepCopy(key, map), deepCopy(value, map));
    28. }
    29. return newMap;
    30. }
    31. if (map.get(originValue)) {
    32. return map.get(originValue);
    33. }
    34. const newObj = Array.isArray(originValue) ? [] : {};
    35. map.set(originValue, newObj);
    36. for (const key in originValue) {
    37. newObj[key] = deepCopy(originValue[key], map);
    38. }
    39. const symbolKeys = Object.getOwnPropertySymbols(originValue);
    40. for (const symbolKey of symbolKeys) {
    41. newObj[Symbol(symbolKey.description)] = deepCopy(originValue[symbolKey], map);
    42. }
    43. return newObj;
    44. }

  • 相关阅读:
    测试/开发程序员该不该跳槽?别忘了当初的梦想......
    【中秋国庆不断更】OpenHarmony组件内状态变量使用:@State装饰器
    IBM MQ 连接属性-示例
    WebSocket: 实时通信的理解与认识
    Redis - 11、集群(Cluster)
    JVM虚拟机:通过日志学习PS+PO垃圾回收器
    setTimeout引发的刨根问底
    Kotlin okhttp3 HttpClient
    Java运算符
    单目3D自动标注
  • 原文地址:https://blog.csdn.net/a15297701931/article/details/126711180