JavaScript深浅copy

124 阅读3分钟

浅copy

"拷贝"是指将一个对象的值复制到另一个对象中。 由于javascript基本数据类型存在栈内存中,所以对于基本数据类型来说,一定是深copy。 以下探究的浅拷贝和深拷贝只是只针对引用类型

1、浅拷贝

浅拷贝仅复制对象的第一层属性,对于嵌套的引用数据类型,它只复制引用地址,而**不复制实际内容

特点

  • 基本数据类型的值会被完全复制。
  • 引用数据类型的值只会复制引用(指针),不会复制实际的内存内容。

实现方式

  1. 使用Object.assign()
const obj1 = { a: 1, b: { c: 2 } };
const shallowCopy = Object.assign({}, obj1);
shallowCopy.b.c = 42; // 修改拷贝对象
console.log(obj1.b.c); // 42,原对象也被影响
  1. 使用展开运算符(...)
const obj1 = { a: 1, b: { c: 2 } };
const shallowCopy = { ...obj1 };
shallowCopy.b.c = 42;
console.log(obj1.b.c); // 42

2、深拷贝

深拷贝会递归复制对象的所有层级属性,包括嵌套的引用数据类型,生成一个与原对象完全独立的副本。

特点:

  • 原对象和拷贝对象完全独立,互不影响。
  • 需要处理多层嵌套的对象和数组。
实现方式一:使用JSON.parse(JSON.stringify())

工作原理‌:通过JSON.stringify()将JavaScript对象转换成JSON字符串,再通过JSON.parse()将JSON字符串转换回JavaScript对象,实现深度拷贝。

const obj1 = { a: 1, b: { c: 2 } };
const deepCopy = JSON.parse(JSON.stringify(obj1));
deepCopy.b.c = 42;
console.log(obj1.b.c); // 2,原对象未受影响

缺点: 无法处理函数、循环引用、特殊对象(如Date、RegExp、undefined等),且会丢失对象的原型链

实现方式二:使用structuredClone

structuredClone 是现代浏览器提供的原生方法,支持多种数据类型的深拷贝

const obj = {
    num: 42,
    str: "hello",
    func: function (x) { return x * 2; },
    date: new Date(),
    nested: { arr: [1, 2, 3] },
    map: new Map([["key", "value"]]),
    set: new Set([1, 2, 3]),
};

const clonedObj = structuredClone(obj);

console.log(clonedObj);
console.log(clonedObj.date === obj.date); // false
console.log(clonedObj.map === obj.map); // false

优点:原生支持、性能好、支持 DateMapSet 等常见对象

缺点:不支持函数、兼容性受限(需在现代浏览器或 Node.js 17+ 环境)

实现方式三:基于 lodashcloneDeep

lodashcloneDeep 是一个成熟的深拷贝工具,适合复杂对象的复制。

import _ from 'lodash';

const obj = {
    num: 42,
    str: "hello",
    func: function (x) { return x * 2; },
    date: new Date(),
    nested: { arr: [1, 2, 3] },
    map: new Map([["key", "value"]]),
    set: new Set([1, 2, 3]),
};

const clonedObj = _.cloneDeep(obj);

console.log(clonedObj);
console.log(clonedObj.func === obj.func); // true,函数只拷贝引用
console.log(clonedObj.date === obj.date); // false

优点:支持几乎所有常见类型的深拷贝,社区广泛使用,经过充分测试

缺点:需要引入第三方库、函数仍然是引用拷贝

实现方式四:递归遍历
function deepCopy(value, hash = new WeakMap()) {
    // 检查是否是原始类型(原始类型直接返回)
    if (value === null || (typeof value !== 'object' && typeof value !== 'function')) return value; 
    if (hash.has(value)) return hash.get(value); // 处理循环引用
    let clone;
    if(value instanceof Date){
        clone = new Date(value);
    }else if(value instanceof RegExp){
        clone = new RegExp(value.source, value.flags)
    }else if(value instanceof Map){
        clone = new Map();
        hash.set(value, clone);
        for(const [key, val] of value){
            clone.set(deepCopy(key, hash), deepCopy(val, hash));
        }
    }else if(value instanceof Set){
        clone = new Set();
        hash.set(value, clone);
        for(const val of value){
          clone.add(deepCopy(val, hash));
        }
    } else if(typeof value === 'function'){
        clone = new Function( value.toString())()
    }else{
        clone = Array.isArray(value) ? [] : {};
        hash.set(value, clone); // 将当前对象存入 WeakMap,防止循环引用
        // 遍历对象或数组并递归拷贝
        for (const key in value) {
            if (Object.prototype.hasOwnProperty.call(value, key)) {
                clone[key] = deepCopy(value[key], hash);
            }
        }
        // 拷贝对象的Symbol键
        const symbols = Object.getOwnPropertySymbols(value);
        for(let sym of symbols){
            clone[sym] = deepCopy(value[sym], hash)
        }
    }
    return clone;
}
const symbol1 = Symbol('a');
// 示例用法
const obj = {
    a: "Alice",
    b: 25,
    c: ["reading", "traveling"],
    e: {
    a: 1,
    b: [1, 2, 3],
    },
    f: new Date(),
    g: /abc/g,
    h: new Map(),
    i: new Set(),
    [symbol1]: {a:3},
    k: (x) => x * 2,
};

const copiedObj = deepCopy(obj);
console.log(copiedObj);
console.log(copiedObj.e === obj.e); // false
console.log(copiedObj.f === obj.f); // false
console.log(copiedObj.e.b === obj.e.b); // false
console.log(copiedObj[symbol1] === obj[symbol1]); // false
console.log(copiedObj.k === obj.k); // false