乐趣区

关于javascript:js深浅拷贝方式整理

浅拷贝

Object.assign

object.assign 的语法为:Object.assign(target, ...sources)

Object.assign特点

  1. 它不会拷贝对象的继承属性;
  2. 它不会拷贝对象的不可枚举的属性;
  3. 能够拷贝 Symbol 类型的属性。

    扩大运算符形式

    扩大运算符的语法为:let cloneObj = {...obj};

    扩大运算符拷贝形式和 Object.assign 相似,都只是进行浅拷贝,如果属性都是根本类型的值,应用扩大运算符进行浅拷贝会很不便。

    concat 拷贝数组

    let arr = [1,2];
    let newArr = arr.concat();
    newArr[1] = 10;
    console.log(arr);  // [1, 2]
    console.log(newArr); // [1, 10]

    应用场景有局限性

    slice 拷贝数组

    slice 的语法为:arr.slice(begin, end);

    会返回一个新数组,不扭转原数组,只能拷贝一层对象,嵌套类型无能为力。

实现一个浅拷贝
思路如下

  1. 对根底类型做一个最根本的一个拷贝;
  2. 对援用类型开拓一个新的存储,并且拷贝一层对象属性。
function shallowClone(target) {if (typeof target === object && target !== null) {const clone = Array.isArray(target) ? [] : {}
        for (let i in target) {if (target.hasOwnProperty(i)) {clone[i] = target[i]
            }
        }
        return clone;
    } else {return target}
}

深拷贝

深拷贝原理:将一个对象从内存中残缺地拷贝进去一份给指标对象,并从堆内存中开拓一个全新的空间寄存新对象,且新对象的批改并不会扭转原对象,二者实现真正拆散

JSON.stringify

这种形式比拟常见,应用也比较简单

let obj1 = {a:1, b:[1,2,3] }
let str = JSON.stringify(obj1);let obj2 = JSON.parse(str);

存在缺点

  1. 拷贝的对象的值中如果有函数、undefined、symbol 这几种类型,通过 JSON.stringify 序列化之后的字符串中这个键值对会隐没;
  2. 拷贝 Date 援用类型会变成字符串;
  3. 无奈拷贝不可枚举的属性;
  4. 无奈拷贝对象的原型链;
  5. 拷贝 RegExp 援用类型会变成空对象;
  6. 对象中含有 NaN、Infinity 以及 -Infinity,JSON 序列化的后果会变成 null;
  7. 无奈拷贝对象的循环利用,即对象成环 (obj[key] = obj)。

递归形式拷贝

理论利用中比拟常见,这种形式应用也比拟频繁

function deepClone(obj) {let clone = {};
    for (let key in obj) {if (typeof obj[key] === "object") {clone[key] = deepClone(obj[key])
        } else {clone[key] = obj[key]
        }
    }
    return clone;
}

存在问题

  1. 这个深拷贝函数并不能复制不可枚举的属性以及 Symbol 类型;
  2. 这种办法只是针对一般的援用类型的值做递归复制,而对于 Array、Date、RegExp、Error、Function 这样的援用类型并不能正确地拷贝;
  3. 对象的属性外面成环,即循环援用没有解决。

改良版本深拷贝

针对以上拷贝存在的问题。
改良版本新增解决项

  1. 针对可能遍历对象的不可枚举属性以及 Symbol 类型,咱们能够应用 Reflect.ownKeys 办法;
  2. 当参数为 Date、RegExp 类型,则间接生成一个新的实例返回;
  3. 利用 Object 的 getOwnPropertyDescriptors 办法能够取得对象的所有属性,以及对应的个性,顺便联合 Object 的 create 办法创立一个新对象,并继承传入原对象的原型链;
  4. 利用 WeakMap 类型作为 Hash 表,因为 WeakMap 是弱援用类型,能够无效避免内存透露(你能够关注一下 Map 和 weakMap 的要害区别,这里要用 weakMap),作为检测循环援用很有帮忙,如果存在循环,则援用间接返回 WeakMap 存储的值。
Reflect.ownKeys 办法返回一个由指标对象本身的属性键组成的数组。它的返回值等同于 Object.getOwnPropertyNames(target).concat(Object.getOwnPropertySymbols(target))。Object.getOwnPropertyDescriptors。这个办法次要的作用是返回属性的形容对象(descriptor)WeakMap 和 Map 相似,然而有区别:1、WeakMap 只承受对象作为 key,如果设置其余类型的数据作为 key,会报错。2、WeakMap 的 key 所援用的对象都是弱援用,只有对象的其余援用被删除,垃圾回收机制就会开释该对象占用的内存,从而防止内存透露。3、因为 WeakMap 的成员随时可能被垃圾回收机制回收,成员的数量不稳固,所以没有 size 属性。4、没有 clear()办法
5、不能遍历

代码实现

// 判断是否是简单数据类型
const isComplexDataType = obj => {(typeof obj === "object" || typeof obj === "function") && (obj !== null)
}
const deepClone = function (obj, hash = new WeakMap()) {
    // 非凡对象解决
    if (obj.constructor === Date)
        return new Date(obj)
    if (obj.constructor === RegExp)
        return new RegExp(obj)
    // 如果循环援用了就用 WeakMap 来解决
    if (hash.get(obj))
        return hash.get(obj)
    let allDesc = Object.getOwnPropertyDescriptors(obj);
    // 遍历传入参数所有键的个性
    let clone = Object.create(Object.getPrototypeOf(obj), allDesc)
    // 继承原型链
    hash.set(obj, clone)
    for (let key of Reflect.ownKeys(boj)) {clone[key] = (isComplexDataType(obj[key]) && typeof obj[key] !== "function") ? deepClone(obj[key], hash) : obj[key]
    }
    return clone 
}
退出移动版