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

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

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

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

2.1 浅拷贝的实现方法

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

javascript 复制代码
// 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 } }
  1. 使用展开运算符赋值
javascript 复制代码
// 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 } }
  1. 对数组可以使用slice或者concat方法
javascript 复制代码
// 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. 处理基本类型和引用类型
javascript 复制代码
function deepClone(value){
    // 处理基本类型(非引用类型)和null
    if(typeof value !== 'object' || value === null){
        return value
    }
}
  1. 处理日期Date对象和正则RegExp对象
javascript 复制代码
// 处理正则对象
function deepClone(value){
    // ...上一步代码

    // Date对象和正则对象直接通过new的方式创建一个新的对象并返回
    if(value instanceof RegExp){
        return new RegExp(value)
    }
    if(value instanceof Date){
        return new Date(value)
    }
}
  1. 处理函数(函数一般不进行深拷贝,闭包所设计的引用过于复杂)
javascript 复制代码
function deepClone(value){
    //...前面代码    

    // 处理函数 直接返回,不做处理
    if(typeof value === 'function'){
        return value
    }
}
  1. 初始化拷贝对象
javascript 复制代码
// 初始化拷贝对象
function deepClone(value){
    // ...前面代码

    // 初始化拷贝对象
    const copy = Array.isArray(value) ? [] : {}
}
  1. 处理循环引用:使用WeakMap记录拷贝过的对象,防止循环引用
javascript 复制代码
// 处理循环引用
function deepClone(value, cache = new WeakMap()){
    //...前面代码

    // 如果拷贝过的对象,直接返回
    if(cache.get(value)){
        return cache.get(value)
    }
    // 缓存拷贝对象
    cache.set(value, copy)
}
  1. 递归拷贝对象的属性:使用 Reflect.ownKeys 获取对象的所有属性,包括不可枚举属性和 Symbol 属性,然后递归地拷贝每个属性:
javascript 复制代码
// 递归拷贝对象属性
function deepClone(value, cache = new WeakMap()){
    // ...上面的代码

    // 递归拷贝对象属性
    const keys = Reflect.ownKeys(value)
    for(let key of keys){
        copy[key] = deepClone(value[key], cache)
    }
    return copy
}
  1. 处理 Map和Set
javascript 复制代码
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
    }

    // ...后续代码
}
手写深拷贝完整代码
javascript 复制代码
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)),但是会造成部分数据丢失,且无法处理特殊对象。

javascript 复制代码
// deep copt use Json.parse and Json.stringify
const source = { b: 4, c: {d : 9} };
const deepCopy = JSON.parse(JSON.stringify(source));
深拷贝测试示例
javascript 复制代码
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等类型数据的丢失
相关推荐
L.S.V.2 分钟前
Java 溯本求源之基础(三十)——封装,继承与多态
java·开发语言
游客5209 分钟前
设计模式-创建型-工厂方法模式
开发语言·python·设计模式·工厂方法模式
m0_7482349013 分钟前
Hmsc包开展群落数据联合物种分布模型分析通用流程(Pipelines)
开发语言·python
发呆的薇薇°21 分钟前
react里使用Day.js显示时间
前端·javascript·react.js
WongKyunban22 分钟前
bash shell脚本while循环
开发语言·bash
想成为高手49925 分钟前
华为仓颉编程语言的函数与结构类型分析
开发语言·华为
跑跑快跑25 分钟前
React vite + less
前端·react.js·less
web1368856587134 分钟前
ctfshow_web入门_命令执行_web29-web39
前端
lly20240641 分钟前
Ruby 数据库访问 - DBI 教程
开发语言
GISer_Jing42 分钟前
前端面试题合集(一)——HTML/CSS/Javascript/ES6
前端·javascript·html