ES6 Object 对象深浅拷贝

@author ixenos

1. 浅拷贝

1.1 Object.assign

Object.assign(target, ...sources)

1.2 拓展运算符

const target = {...source};

1.3 数组相关

Array.prototype.concat
Array.prototype.slice

2. 深拷贝

2.1 JSON序列化反序列化

  • 适用条件
    • 无函数属性
      • 任意的函数在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成 null(出现在数组中时)。
      • 函数被单独转换时,会返回 undefined,如JSON.stringify((function(){}))
    • 无循环引用
      • 确定对象属性引用的对象不具有指向本对象的属性, 如a.b = b, b.a = a
    • 无不可枚举属性
    • 键值会消失:对象的值中为Function、Undefined、Symbol 这几种类型,。
    • 键值变成空对象:对象的值中为Map、Set、RegExp这几种类型。
JSON.parse(JSON.stringify(obj))

2.2 基础递归赋值

  • 适用条件
    • 无循环引用
    • 无不可枚举属性
function deepClone(obj) {
    if(obj === null) return null 
    if(typeof obj !== 'object') return obj;
    var newObj = new obj.constructor ();  //保持继承链
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {   //不遍历其原型链上的属性
            var val = obj[key];
            newObj[key] = typeof val === 'object' ? arguments.callee(val) : val; // 使用arguments.callee解除与函数名的耦合
        }
    }  
    return newObj;  
}

2.3 全面递归赋值

基础递归赋值存在的问题

  • 不能处理循环引用
    使用 WeakMap 作为一个Hash表来进行查询
  • 只考虑了Object对象
    当参数为 Date、RegExp 、Function、Map、Set,则直接生成一个新的实例返回
  • 属性名为Symbol的属性
  • 丢失了不可枚举的属性
    针对能够遍历对象的不可枚举属性以及 Symbol 类型,我们可以使用 Reflect.ownKeys()
    注:Reflect.ownKeys(obj)相当于[...Object.getOwnPropertyNames(obj), ...Object.getOwnPropertySymbols(obj)]
  • 原型上的属性 Object.getOwnPropertyDescriptors()设置属性描述对象,以及Object.create()方式继承原型链
function deepClone(target) {
    // WeakMap作为记录对象Hash表(用于防止循环引用)
    const map = new WeakMap()

    // 判断是否为object类型的辅助函数,减少重复代码
    function isObject(target) {
        return (typeof target === 'object' && target ) || typeof target === 'function'
    }

    function clone(data) {

        // 基础类型直接返回值
        if (!isObject(data)) {
            return data
        }

        // 日期或者正则对象则直接构造一个新的对象返回
        if ([Date, RegExp].includes(data.constructor)) {
            return new data.constructor(data)
        }

        // 处理函数对象
        if (typeof data === 'function') {
            return new Function('return ' + data.toString())()
        }

        // 如果该对象已存在,则直接返回该对象
        const exist = map.get(data)
        if (exist) {
            return exist
        }

        // 处理Map对象
        if (data instanceof Map) {
            const result = new Map()
            map.set(data, result)
            data.forEach((val, key) => {
                // 注意:map中的值为object的话也得深拷贝
                if (isObject(val)) {
                    result.set(key, clone(val))
                } else {
                    result.set(key, val)
                }
            })
            return result
        }

        // 处理Set对象
        if (data instanceof Set) {
            const result = new Set()
            map.set(data, result)
            data.forEach(val => {
                // 注意:set中的值为object的话也得深拷贝
                if (isObject(val)) {
                    result.add(clone(val))
                } else {
                    result.add(val)
                }
            })
            return result
        }

        // 收集键名(考虑了以Symbol作为key以及不可枚举的属性)
        const keys = Reflect.ownKeys(data)
        // 利用 Object 的 getOwnPropertyDescriptors 方法可以获得对象的所有属性以及对应的属性描述
        const allDesc = Object.getOwnPropertyDescriptors(data)
        // 结合 Object 的 create 方法创建一个新对象,并继承传入原对象的原型链, 这里得到的result是对data的浅拷贝
        const result = Object.create(Object.getPrototypeOf(data), allDesc)

        // 新对象加入到map中,进行记录
        map.set(data, result)

        // Object.create()是浅拷贝,所以要判断并递归执行深拷贝
        keys.forEach(key => {
            const val = data[key]
            if (isObject(val)) {
                // 属性值为 对象类型 或 函数对象 的话也需要进行深拷贝
                result[key] = clone(val)
            } else {
                result[key] = val
            }
        })
        return result
    }

    return clone(target)
}

参考博文

posted @ 2022-01-17 17:00  ixenos  阅读(274)  评论(0编辑  收藏  举报