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等类型数据的丢失
相关推荐
明月与玄武5 分钟前
2025 前端框架决战:Vue 与 React 分析优缺点及使用场景!
前端·vue.js·react.js
Matlab程序猿小助手16 分钟前
【MATLAB源码-第303期】基于matlab的蒲公英优化算法(DO)机器人栅格路径规划,输出做短路径图和适应度曲线.
开发语言·算法·matlab·机器人·kmeans
不爱编程的小九九16 分钟前
小九源码-springboot097-java付费自习室管理系统
java·开发语言·spring boot
无盐海20 分钟前
XSS漏洞攻击 (跨站脚本攻击)
前端·xss
不一样的少年_26 分钟前
1024程序员节:用不到100行代码做个“代码雨屏保”装X神器(附源码)
前端·javascript·浏览器
云知谷28 分钟前
【经典书籍】C++ Primer 第16章模板与泛型编程精华讲解
c语言·开发语言·c++·软件工程·团队开发
阿奇__31 分钟前
el-table默认排序设置
前端·javascript·vue.js
hongc9336 分钟前
element-ui el-table 设置固定列fixed 高度不对
前端·vue.js·elementui
Forfun_tt1 小时前
xss-labs pass-12
前端·xss
workflower1 小时前
基本作业-管理⾃⼰的源代码
开发语言·单元测试·软件工程·需求分析·个人开发