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 
}

你可能感兴趣的:(javascript前端es6)