首页 > 其他分享 >深拷贝和浅拷贝的实现方法

深拷贝和浅拷贝的实现方法

时间:2023-01-07 23:00:45浏览次数:36  
标签:obj 实现 Object let key console 拷贝 方法

深拷贝

1:

// 循环递归法1 
function isObject(obj) {
    return (typeof obj === 'object' || typeof obj === 'function') && obj !== null
}
// 迭代递归法:深拷贝对象与数组
function deepClone(obj) {
    if (!isObject(obj)) {
        throw new Error('obj 不是一个对象!')
    }

    let isArray = Array.isArray(obj)

    let cloneObj = isArray ? [] : {}

    for (let key in obj) {
        cloneObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key]
    }

    return cloneObj
}
let sym = Symbol('我是一个Symbol')
let obj1 = {
    a: {
        b: 1,

    },
}
obj1[sym] = 111
let obj2 = deepClone(obj1)
console.log(obj1)
console.log(obj2)
/* 
深拷贝是针对引用类型的, 在进行深拷贝之前, 我们应该先知道js中有哪些引用类型,
js中引用类型目前有六种: object, array, date, regexp,
function, err。 下面的两种方法只能实现object, array的深拷贝。
循环递归法 function、Date、RegExp 和Error无法复制 因为它们有特殊的构造函数。
*/

2:

// 循环递归法2
function isObject(o) {
    return (typeof o === 'object' || typeof o === 'function') && o !== null
}

function deepClone(obj) {
    if (!isObject(obj)) {
        throw new Error('obj 不是一个对象!')
    }

    let isArray = Array.isArray(obj)
    let cloneObj = isArray ? [...obj] : {
        ...obj
    }
    // Reflect.ownKeys 返回对象的所有属性
    Reflect.ownKeys(cloneObj).forEach(key => {
        cloneObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key]
    })

    return cloneObj
}

let sym = Symbol('我是一个Symbol')
let obj1 = {
    a: {
        b: 1,

    },
}
obj1[sym] = 111
let obj2 = deepClone(obj1)
console.log(obj1)
console.log(obj2)

 

3:

// 序列化反序列化法 使用JSON对象的parse和stringify方法来实现深拷贝
function deepClone(obj) {
    let _obj = JSON.stringify(obj),
        objClone = JSON.parse(_obj);
    return objClone
}
let obj1 = {
    a: {
        b: 1
    }
};
Object.defineProperty(obj1, 'innumerable', {
    value: '不可枚举属性',
    enumerable: false
});
let obj2 = deepClone(obj1)
console.log(obj1)
console.log(obj2)
// 它也只能深拷贝对象和数组,对于其他种类的对象,会失真。
// 这种方法比较适合平常开发中使用,因为通常不需要考虑对象和数组之外的类型。

// 拷贝的对象的值中如果有函数,undefined,symbol则经过JSON.stringify()序列化后的JSON字符串中这个键值对会消失
// 无法拷贝不可枚举的属性, 无法拷贝对象的原型链
// 拷贝Date引用类型会变成字符串
// 拷贝RegExp引用类型会变成空对象
// 对象中含有NaN、 Infinity和 - Infinity, 则序列化的结果会变成null
// 无法拷贝对象的循环应用(即obj[key] = obj)

 

4:

// 3. lodash中深拷贝的实现
// Lodash是一个轻量级的JavaScript工具函数库, 它方便了日常开发中对数据的操作, 提高了开发效率。
// 著名的 lodash 中的 cloneDeep 方法同样是使用 Reflect 法 实现的, 
// 只不过它支持的对象种类更多, 具体的实现过程读者可以参考 lodash 的 baseClone 方法。 
// lodash可以完成array、 object、 date、 regexp的深拷贝, 但

// function 和 error 仍然不可拷贝

// https://github.com/lodash/lodash/blob/master/.internal/baseClone.js
// ​ 日常开发中,通常会对数据,特别是数组和对象进行各种读写等操作:
//  比如去重,拷贝,合并,过滤,求交集,求和等等。根据平时开发中对数据的操作,
const _ = require('lodash')
const obj = {
    a: 1
}
obj.loopObj = obj
const obj1 = _.cloneDeep(obj)
console.log(obj1 === obj)

 

5:

/*
对象成环怎么办?
我们给 test 加一个 loopObj 键,值指向自身:
test.loopObj = test
这时我们使用第一种方法中的 for..in 实现和 Reflect 实现都会栈溢出:

环对象深拷贝报错 
而使用第二种方法也会报错:


但 lodash 却可以得到正确结果:


因为 lodash 使用的是栈把对象存储起来了,如果有环对象,就会从栈里检测到,
从而直接返回结果,悬崖勒马。这种算法思想来源于 HTML5 规范定义的结构化克隆算法,
它同时也解释了为什么 lodash 不对 Error 和 Function 类型进行拷贝。


当然,设置一个哈希表存储已拷贝过的对象同样可以达到同样的目的:
*/


function deepClone(obj, hash = new WeakMap()) {
    if (!isObject(obj)) {
        return obj
    }
    // 查表
    if (hash.has(obj)) return hash.get(obj)

    let isArray = Array.isArray(obj)
    let cloneObj = isArray ? [] : {}
    // 哈希表设值
    hash.set(obj, cloneObj)

    let result = Object.keys(obj).map(key => {
        return {
            [key]: deepClone(obj[key], hash)
        }
    })
    return Object.assign(cloneObj, ...result)
}

//这里我们使用 WeakMap 作为哈希表,因为它的键是弱引用的,而我们这个场景里键恰好是对象,需要弱引用。

 

6:

// 键值不是字符串而是 Symbol

var test = {}
let sym = Symbol('我是一个Symbol')
test[sym] = 'symbol'

let result = deepClone(test)
console.log(result)
console.log(result[sym] === test[sym])
// 拷贝失败了, 为什么?

// 因为 Symbol 是一种特殊的数据类型, 它最大的特点便是独一无二, 所以它的深拷贝就是浅拷贝

 

7:

/* 
但如果这时我们使用 Reflect 实现的版本:
成功了,因为 for...in 无法获得 Symbol 类型的键,而 Reflect 是可以获取的。

当然,我们改造一下 for...in 实现也可以:

*/
function deepClone(obj) {
    if (!isObject(obj)) {
        throw new Error('obj 不是一个对象!')
    }

    let isArray = Array.isArray(obj)
    let cloneObj = isArray ? [] : {}
    let symKeys = Object.getOwnPropertySymbols(obj)
    // console.log(symKey)
    if (symKeys.length > 0) {
        symKeys.forEach(symKey => {
            cloneObj[symKey] = isObject(obj[symKey]) ? deepClone(obj[symKey]) : obj[symKey]
        })
    }
    for (let key in obj) {
        cloneObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key]
    }

    return cloneObj
}

 

8:

// for...in 会追踪原型链上的属性, // 而其它三种方法(Object.keys、Reflect.ownKeys 和 JSON 方法)都不会追踪原型链上的属性:

9:

// 需要拷贝不可枚举的属性
// 第四种情况, 就是我们需要拷贝类似属性描述符, setters 以及 getters 这样不可枚举的属性, 一般来说, 这就需要一个额外的不可枚举的属性集合来存储它们。 类似在第二种情况使用
// for... in 拷贝 Symbol 类型键时:
// 我们给 test 变量里的 obj 和 arr 属性定义一下属性描述符:
Object.defineProperties(test, {
    'obj': {
        writable: false,
        enumerable: false,
        configurable: false
    },
    'arr': {
        get() {
            console.log('调用了get')
            return [1, 2, 3]
        },
        set(val) {
            console.log('调用了set')
        }
    }
})
// 然后实现我们的拷贝不可枚举属性的版本:
function deepClone(obj, hash = new WeakMap()) {
    if (!isObject(obj)) {
        return obj
    }
    // 查表,防止循环拷贝
    if (hash.has(obj)) return hash.get(obj)

    let isArray = Array.isArray(obj)
    // 初始化拷贝对象
    let cloneObj = isArray ? [] : {}
    // 哈希表设值
    hash.set(obj, cloneObj)
    // 获取源对象所有属性描述符
    let allDesc = Object.getOwnPropertyDescriptors(obj)
    // 获取源对象所有的 Symbol 类型键
    let symKeys = Object.getOwnPropertySymbols(obj)
    // 拷贝 Symbol 类型键对应的属性
    if (symKeys.length > 0) {
        symKeys.forEach(symKey => {
            cloneObj[symKey] = isObject(obj[symKey]) ? deepClone(obj[symKey], hash) : obj[symKey]
        })
    }

    // 拷贝不可枚举属性,因为 allDesc 的 value 是浅拷贝,所以要放在前面
    cloneObj = Object.create(
        Object.getPrototypeOf(cloneObj),
        allDesc
    )
    // 拷贝可枚举属性(包括原型链上的)
    for (let key in obj) {
        cloneObj[key] = isObject(obj[key]) ? deepClone(obj[key], hash) : obj[key];
    }

    return cloneObj
}

 

10:

/* 
1. 日常深拷贝,建议序列化反序列化方法。
2. 面试时遇见面试官搞事情,写一个能拷贝自身可枚举、自身不可枚举、自身 Symbol 类型键、原型上可枚举、原型上不可枚举、原型上的 Symol 类型键,循环引用也可以拷的深拷贝函数:
3. 有特殊需求的深拷贝,建议使用 lodash 的 copyDeep 或 copyDeepWith 方法。
*/
// 将之前写的 deepClone 函数封装一下
function cloneDeep(obj) {
    let family = {}
    let parent = Object.getPrototypeOf(obj)

    while (parent != null) {
        family = completeAssign(deepClone(family), parent)
        parent = Object.getPrototypeOf(parent)
    }

    // 下面这个函数会拷贝所有自有属性的属性描述符,来自于 MDN
    // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
    function completeAssign(target, ...sources) {
        sources.forEach(source => {
            let descriptors = Object.keys(source).reduce((descriptors, key) => {
                descriptors[key] = Object.getOwnPropertyDescriptor(source, key)
                return descriptors
            }, {})

            // Object.assign 默认也会拷贝可枚举的Symbols
            Object.getOwnPropertySymbols(source).forEach(sym => {
                let descriptor = Object.getOwnPropertyDescriptor(source, sym)
                if (descriptor.enumerable) {
                    descriptors[sym] = descriptor
                }
            })
            Object.defineProperties(target, descriptors)
        })
        return target
    }

    return completeAssign(deepClone(obj), family)
}

 

浅拷贝

1:

let target = {}
let obj = {
    a: {
        b: 3
    }
};
Object.assign(target, obj);
console.log(target); //{a:1}

obj.a.b = 1;

console.log(obj); //{a:2}
console.log(target); //{a:1}

2:

let obj1 = {
    a: {
        b: 1
    },
    sym: Symbol(1)
};
Object.defineProperty(obj1, 'innumerable', {
    value: '不可枚举属性',
    enumerable: false
});
let obj2 = {};

Object.assign(obj2, obj1)

obj1.a.b = 2;
console.log('obj1', obj1);
console.log('obj2', obj2);

3:

let obj = {
    a: 1,
    b: {
        c: 1
    }
}
let obj2 = {
    ...obj
};
obj.a = 2;
console.log(obj); //{a:2,b:{c:1}}
console.log(obj2); //{a:1,b:{c:1}}

obj.b.c = 2;
console.log(obj); //{a:2,b:{c:2}}
console.log(obj2); //{a:1,b:{c:2}}

 

标签:obj,实现,Object,let,key,console,拷贝,方法
From: https://www.cnblogs.com/z-bky/p/17033800.html

相关文章