自学内容网 自学内容网

js中的深拷贝与浅拷贝 手写深拷贝代码

1 什么是深拷贝和浅拷贝?

        深拷贝和浅拷贝都是复制对象时常用的两种方式,区别在于对于嵌套对象的处理,浅拷贝只复制属性的第一层属性,双方修改嵌套对象将会互相影响。深拷贝会递归复制每一层的属性,修改任意一方互不影响。

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

2.1 浅拷贝的实现方法

        1.使用Object.assign()方法,接收两个参数(target, source),将source上的第一层属性复制到target的第一层属性上(不改变原有属性,如果重名将会被覆盖,常用于配置对象的修改)。

// Object.assign
// Object.assign() copies property values from a source object to a target object. For example, consider the following code:
const source = { b: 4, c: {d : 9} };
const shallowCopy = Object.assign({}, source);

shallowCopy.c.d = 10;
console.log(source); // { b: 4, c: { d: 10 } }

         2. 使用展开运算符赋值

// use ... (spread operator) for shallow copy
const source = { b: 4, c: {d : 9} };
const shallowCopy = {...source};

shallowCopy.c.d = 10;
console.log(source); // { b: 4, c: { d: 10 } }

        3. 对数组可以使用slice或者concat方法

// Array use slice or concat for shallow copy
const source = [1, 2, 3];
const shallowCopy_01 = source.slice();
// or
const shallowCopy_02 = source.concat();
shallowCopy_01[0] = 100;
shallowCopy_02[0] = 100;
console.log(source); // [1, 2, 3]

2.2 深拷贝的实现方法-包含手写实现

        方法一:使用递归手写实现,具体实现步骤如下所示:

        1. 处理基本类型和引用类型

function deepClone(value){
    // 处理基本类型(非引用类型)和null
    if(typeof value !== 'object' || value === null){
        return value
    }
}

        2.  处理日期Date对象和正则RegExp对象

// 处理正则对象
function deepClone(value){
    // ...上一步代码

    // Date对象和正则对象直接通过new的方式创建一个新的对象并返回
    if(value instanceof RegExp){
        return new RegExp(value)
    }
    if(value instanceof Date){
        return new Date(value)
    }
}

         3. 处理函数(函数一般不进行深拷贝,闭包所设计的引用过于复杂)

function deepClone(value){
    //...前面代码    

    // 处理函数 直接返回,不做处理
    if(typeof value === 'function'){
        return value
    }
}

        4.  初始化拷贝对象

// 初始化拷贝对象
function deepClone(value){
    // ...前面代码

    // 初始化拷贝对象
    const copy = Array.isArray(value) ? [] : {}
}

        5.  处理循环引用:使用WeakMap记录拷贝过的对象,防止循环引用

// 处理循环引用
function deepClone(value, cache = new WeakMap()){
    //...前面代码

    // 如果拷贝过的对象,直接返回
    if(cache.get(value)){
        return cache.get(value)
    }
    // 缓存拷贝对象
    cache.set(value, copy)
}

        6.  递归拷贝对象的属性:使用 Reflect.ownKeys 获取对象的所有属性,包括不可枚举属性和 Symbol 属性,然后递归地拷贝每个属性:

// 递归拷贝对象属性
function deepClone(value, cache = new WeakMap()){
    // ...上面的代码

    // 递归拷贝对象属性
    const keys = Reflect.ownKeys(value)
    for(let key of keys){
        copy[key] = deepClone(value[key], cache)
    }
    return copy
}

        7. 处理 Map和Set

function deepClone(value, cache = new WeakMap()){
    // ...上面代码
    // 缓存拷贝对象
    cache.set(value, copy)

    // 处理Map和Set
    if(value instanceof Map){
        const copy = new Map()
        value.forEach((val, key) => {
            copy.set(key, deepClone(val, cache))
        })
        return copy
    }

    if(value instanceof Set){
        const copy = new Set()
        value.forEach(val => {
            copy.add(deepClone(val, cache))
        })
        return copy
    }

    // ...后续代码
}
手写深拷贝完整代码
function deepClone(value, cache = new WeakMap()){
    // 处理基本类型(非引用类型)和null,直接返回即可
    if(typeof value !== 'object' || value === null){
        return value
    }

    // Date对象和正则对象直接通过new的方式创建一个新的对象并返回
    if(value instanceof RegExp){
        return new RegExp(value)
    }
    if(value instanceof Date){
        return new Date(value)
    }

    // 处理函数 直接返回,不做处理
    if(typeof value === 'function'){
        return value
    }

    // 初始化拷贝对象(保留对象原型链)
    const copy = Array.isArray(value) ? [] : Object.create(Object.getPrototypeOf(value))

    // 如果拷贝过的对象,直接返回
    if(cache.get(value)){
        return cache.get(value)
    }
    // 缓存拷贝对象
    cache.set(value, copy)

    // 处理Map和Set
    if(value instanceof Map){
        const copy = new Map()
        value.forEach((val, key) => {
            copy.set(key, deepClone(val, cache))
        })
        return copy
    }

    if(value instanceof Set){
        const copy = new Set()
        value.forEach(val => {
            copy.add(deepClone(val, cache))
        })
        return copy
    }

    // 递归拷贝对象属性(包含不可枚举属性)
    const keys = Reflect.ownKeys(value)
    for(let key of keys){
        copy[key] = deepClone(value[key], cache)
    }

    return copy
}

         方法二:使用JSON.parse(JSON.stringify(obj)),但是会造成部分数据丢失,且无法处理特殊对象。

// deep copt use Json.parse and Json.stringify
const source = { b: 4, c: {d : 9} };
const deepCopy = JSON.parse(JSON.stringify(source));
深拷贝测试示例
const obj = {
  num: 1,
  str: 'string',
  bool: true,
  nullValue: null,
  undefinedValue: undefined,
  symbol: Symbol('sym'),
  date: new Date(),
  regExp: /\w+/g,
  func: function () { console.log('function'); },
  arr: [1, 2, { a: 3 }],
  obj: { x: 10, y: { z: 20 } },
  map: new Map([['key1', 'value1'], ['key2', { a: 1 }]]),
  set: new Set([1, 2, { b: 3 }]),
  [Symbol('symbolKey')]: 'symbolValue',
};

obj.circularRef = obj; // 添加循环引用

const clonedObj = deepClone(obj);

console.log(clonedObj);

        测试结果如下所示:

 

3 深拷贝需要注意的问题

        实现深拷贝时需要考虑以下问题:

  • 循环引用问题:如果对象内部引用自己,不考虑该情况将会导致无限递归。
  • 性能损耗:深拷贝大型对象和数组将会浪费大量性能,带来卡顿。
  • 考虑特殊对象类型:例如Date RegExp Set Map Function等对象需要特殊处理
  • 不可枚举属性和原型链:只会复制对象的可枚举属性
  • 某些深拷贝造成的数据丢失:使用JSON.parse(JSON.stringify(obj))时造成undefined Symbol Funcion等类型数据的丢失

原文地址:https://blog.csdn.net/qq_33546823/article/details/142639217

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!