• JavaScript中的浅拷贝和深拷贝


     浅拷贝

    创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。       

    默认情况下:引用类型(object)都是浅拷贝

    简单理解:对于对象来说,就是对最外层数据的拷贝,如果对象只有一层,则相当于深拷贝,如果对象有多层,即某个属性为引用数据类型,则拷贝的是该引用类型在堆中的地址,不会在内存创建一个新的对象。

    常见的对象浅拷贝

    1. Object.assign

    用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。

    2.展开运算符... 

    1. // 对象的浅拷贝
    2. // 1.Object.assign()
    3. let obj0={a:0}
    4. let obj2 = Object.assign(obj0,obj1)
    5. console.log(obj0===obj2) //true
    6. console.log(obj1===obj2) // false
    7. obj1.name = '张三1' //obj2不变
    8. obj1.hobby[0]='打篮球' //obj2变了
    9. obj1.friend.name='李四1' //obj2变了
    10. console.log(obj1)
    11. console.log(obj2)
    12. // 2.展开运算符
    13. let obj3 = {...obj1}
    14. obj1.hobby.push('摄影') //obj3会改变
    15. console.log(obj3)

    如果被拷贝对象的属性值为简单类型(如Number,String),通过Object.assign({},Obj)得到的新对象为深拷贝;如果属性值为对象或其它引用类型,则只拷贝了该引用类型的地址。 

    常见的数组浅拷贝

    1.Array.prototype.concat()

    将alpha和numeric合并为一个新数组并返回

    1. let arr1 = ['哈哈',18,{name:'哈利',age:10}]
    2. let arr2 = ['嘿嘿',19,{name:'赫敏',age:11}]
    3. // 1.concat
    4. let arr3 = arr1.concat(arr2)
    5. console.log(arr3===arr1) //false
    6. console.log(arr3===arr2) // false
    7. arr1[0]='haha' //arr3不会改
    8. arr1[2].age=100 //arr3会改
    9. console.log(arr3)

    2. Array.prototype.slice()

    slice() 方法返回一个新的数组对象,这一对象是一个由 begin 和 end 决定的原数组的浅拷贝(包括 begin,不包括end)。原始数组不会被改变。

    1. // 1.slice截取数组
    2. const animals = ['ant', 'bison', 'camel', 'duck', 'elephant'];
    3. console.log(animals.slice(2));
    4. // expected output: Array ["camel", "duck", "elephant"]
    5. console.log(animals.slice(2, 4));
    6. // expected output: Array ["camel", "duck"]
    7. //2.slice浅拷贝
    8. let arr1 = ['哈哈',18,{name:'哈利',age:10}]
    9. let arr4 = arr1.slice() //不传参数相当于浅拷贝数组
    10. console.log(arr4===arr1) //false
    11. arr1[2].name = '哈利111' //arr4会变
    12. console.log(arr4)

     3 扩展运算符 ...

    1. // 3.展开运算符
    2. let arr1 = ['哈哈',18,{name:'哈利',age:10}]
    3. let arr5 = [...arr1]
    4. arr1[2].age=5 //arr5会改变
    5. console.log(arr5)

    参考:https://github.com/YvetteLau/Step-By-Step/issues/17https://github.com/YvetteLau/Step-By-Step/issues/17

    深拷贝

     将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象不会影响原对象。

    根据拷贝的层级不同可以分为浅拷贝和深拷贝,浅拷贝就是只进行一层拷贝,深拷贝就是无限层级拷贝

    1.深拷贝的简单实现 JSON.parse(JSON.stringify())

    1. // JSON.parse(JSON.stringify(obj))可以处理:
    2. // string,number,boolean,null,object,array
    3. // 会忽略symbol,undefined,function,不能处理bigint,date,error,map,set
    4. // 不能处理循环引用
    5. let obj = {
    6. name: "哈哈",
    7. age: 18,
    8. bool: true,
    9. nu: null,
    10. un: undefined, //会忽略undefined类型的值
    11. symbol: Symbol(), //会忽略symbol类型的值
    12. // big: 15n, Do not know how to serialize a BigInt
    13. map: new Map(), // 不能处理
    14. set: new Set(),// 不能处理
    15. obj: {
    16. color: "blue",
    17. age: 13,
    18. },
    19. arr: [1, 2, 3, 4],
    20. date: new Date(),
    21. err: new Error(),
    22. fun: function add() {}, //会忽略function类型的值
    23. };
    24. let obj1 = JSON.parse(JSON.stringify(obj));
    • 可以处理基本数据类型(string,number,boolean,null),和引用数据类型的(对象,数组)
    • 不能处理函数,undefined,symbol,function,map,set类型的值
    • 并且当对象中包含循环引用时会报错(obj.info = obj)

    3.完整的深拷贝

    封装一个函数来实现:

    1. // 可继续遍历的数据类型
    2. const objectTag = "[object Object]";
    3. const arrayTag = "[object Array]";
    4. const mapTag = "[object Map]";
    5. const setTag = "[object Set]";
    6. // 不可继续遍历的数据类型
    7. const stringTag = "[object String]";
    8. const numberTag = "[object Number]";
    9. const booleanTag = "[object Boolean]";
    10. const symbolTag = "[object Symbol]";
    11. const dateTag = "[object Date]";
    12. const functionTag = "[object Function]";
    13. const errorTag = "[object Error]";
    14. // 需要深度遍历的数据类型
    15. const deepTag = [setTag, mapTag, arrayTag, objectTag];
    16. // 工具函数-遍历数组,使用while提升性能
    17. function forEach(arr, cb) {
    18. const len = arr.length;
    19. let i = -1;
    20. while (++i < length) {
    21. cb(arr[i], i);
    22. }
    23. return arr;
    24. }
    25. // 工具函数-判断是否是引用类型
    26. function isObject(value) {
    27. const type = typeof value;
    28. // 这里忽略了function类型 function类型直接返回
    29. return value !== null && type === "object";
    30. }
    31. // 工具函数-获取数据实际类型
    32. function getType(value) {
    33. return Object.prototype.toString.call(value);
    34. }
    35. //工具函数-初始化被克隆的对象
    36. function getInit(value) {
    37. const cons = value.constructor;
    38. return new cons();
    39. }
    40. // 工具函数-克隆symbol
    41. function cloneSymbol(value) {
    42. return Object(Symbol.prototype.valueOf.call(value));
    43. }
    44. // 工具函数-克隆不可遍历的类型(这里忽略了对函数和正则的处理)
    45. function cloneOtherType(value) {
    46. const type = getType(value);
    47. const ctor = value.constructor;
    48. switch (type) {
    49. case stringTag:
    50. case numberTag:
    51. case booleanTag:
    52. case functionTag:
    53. case errorTag:
    54. case dateTag:
    55. return new ctor(value);
    56. case symbolTag:
    57. return cloneSymbol(value);
    58. default:
    59. return null;
    60. }
    61. }
    62. // 真正实现深拷贝的函数
    63. function clone(value, map = new WeakMap()) {
    64. // 不是引用数据类型直接返回
    65. if (!isObject(value)) return value;
    66. // 根据数据类型做不同的处理
    67. let copyValue;
    68. const typeOfValue = getType(value);
    69. if (deepTag.includes(typeOfValue)) {
    70. copyValue = getInit(value);
    71. } else {
    72. return cloneOtherType(value);
    73. }
    74. // 处理循环引用
    75. if (map.has(value)) return map.get(value);
    76. map.set(value, copyValue);
    77. // 处理Map
    78. if (typeOfValue === mapTag) {
    79. value.forEach((item, key) => {
    80. copyValue.set(key, clone(item, map));
    81. });
    82. return copyValue;
    83. }
    84. // 处理set
    85. if (typeOfValue === setTag) {
    86. set.forEach((item) => {
    87. copyValue.add(clone(item, map));
    88. });
    89. return copyValue;
    90. }
    91. // 处理对象
    92. if (typeOfValue === objectTag) {
    93. const keys = value.keys();
    94. forEach(keys, (item) => {
    95. copyValue[item] = clone(value[item], map);
    96. });
    97. //获取属性值为symbol类型的keys
    98. const symbolKeys = Object.getOwnPropertySymbols(value);
    99. forEach(symbolKeys, (item) => {
    100. copyValue[item] = clone(value[item], map);
    101. });
    102. return copyValue;
    103. }
    104. // 处理数组
    105. if (typeOfValue === arrayTag) {
    106. forEach(value, (item, index) => {
    107. copyValue[index] = clone(item, map);
    108. });
    109. return copyValue;
    110. }
    111. }

    参考:如何写出一个惊艳面试官的深拷贝 (qq.com)

  • 相关阅读:
    【电源专题】开关模式电源电流检测——电流检测方法
    SpringBoot整合RocketMQ,老鸟们都是这么玩的!
    js实现websocket服务端和客户端
    C语言规范
    【Python机器学习基础教程】第三章第三节:预处理与缩放
    笔记 | 嵌入式系统概论
    Lombok插件介绍、MyBatisPlus分页功能、控制台日志及删除banner
    webpack配置css-loader让scss文件支持模块化引入
    React Hook的使用
    Python中的numpy库
  • 原文地址:https://blog.csdn.net/ICanWin_lll/article/details/133685524