JavaScript 拷贝全解析:从浅拷贝到深拷贝的完整指南

引言

在Javascript开发中,数据拷贝是我们每天都会遇到的基础操作。然而,这个看似简单的概念背后隐藏着许多陷阱和细节。错误的数据拷贝可能导致难以调试的bug、内存泄漏甚至程序崩溃。

你是否曾遇到过:

  • 修改一个对象后,另一个"独立"的对象也被意外修改?
  • 尝试复制包含函数、Date对象或循环引用的数据结构时失败?
  • 在处理大型数据集时,拷贝导致性能急剧下降?

本文将从基础概念出发,深入探讨JavaScript中的各种拷贝技术,提供完整的实现方案,并帮助你根据不同的场景选择最合适的拷贝策略。

一、理解JavaScript的数据类型

在深入拷贝之前,我们需要先理解JavaScript的数据类型,因为不同类型的数据在拷贝时有根本性的区别。

1.1 基本类型(Primitive Types)

JavaScript有7种基本数据类型:

javascript 复制代码
// 基本类型 - 按值存储,拷贝时直接复制值
const str = 'Hello';      // String
const num = 42;           // Number
const bool = true;        // Boolean
const nullValue = null;   // Null
const undefinedValue;      // Undefined
const sym = Symbol('id'); // Symbol(ES6)
const bigInt = 123n;      // BigInt(ES2020)
1.2 引用类型(Reference Types)
javascript 复制代码
// 引用类型 - 按引用存储,拷贝时复制引用
const obj = { name: 'John' }; // Object
const arr = [1, 2, 3];        // Array
const func = () => {};        // Function
const date = new Date();      // Date
const regex = /pattern/gi;    // RegExp
const map = new Map();        // Map
const set = new Set();        // Set
1.3 内存模型图解
text 复制代码
// 基本类型 - 栈内存存储
let a = 10; // 栈: a = 10
let b = a;  // 栈: b = 10 (值的拷贝)
b = 20;     // 栈: b = 20, a 保持不变

// 引用类型 - 堆内存存储
let obj1 = { x: 10 }; // 栈: obj1 -> 堆地址1 {x: 10}
let obj2 = obj1;      // 栈: obj2 -> 同一个堆地址1
obj2.x = 20;          // 堆地址1: {x: 20}, obj1.x 也变为 20

理解这个区别是掌握拷贝技术的基础。接下来,我们开始探讨具体的拷贝方法。

二、浅拷贝(Shallow Copy)

浅拷贝创建一个新对象,复制原始对象的所有属性值到新对象。如果属性值是基本类型,则复制值; 如果是引用类型,则复制引用。

2.1 对象浅拷贝方法
方法1: 展开运算符(Spread Operator) - ES6+
javascript 复制代码
const original = {
  name: 'John',
  age: 30,
  hobbies: ['reading', 'gaming'],
  address: {
    city: 'Shang Hai',
    zip: '120001'
  }
};

const shallowCopy = { ...original };

// 测试
console.log(shallowCopy === original); // false - 是新对象
console.log(shallowCopy.hobbies === original.hobbies); // true - 引用相同
console.log(shallowCopy.address === original.address); // true - 引用相同

// 修改嵌套对象会影响原对象
shallowCopy.hobbies.push("coding");
console.log(original.hobbies); // [ 'reading', 'gaming', 'coding' ] - 被影响
方法2: Object.assign() - ES6
javascript 复制代码
const shallowCopy2 = Object.assign({}, original);

// Object.assign 可以合并多个对象
const obj1 = { a: 1 };
const obj2 = { b: 2 };
const obj3 = { c: 3 };
const merged = Object.assign({}, obj1, obj2, obj3);
console.log(merged); // { a: 1, b: 2, c: 3 }
方法3: 手动实现浅拷贝
javascript 复制代码
function shallowCopy(obj) {
  // 处理 null 和 undefined
  if (obj === null || typeof obj !== "object") {
    return obj;
  }

  // 处理数组
  if (Array.isArray(obj)) {
    return [...obj];
  }

  // 处理对象
  const copy = {};
  for (const key in obj) {
    // 只拷贝对象自身的属性(不包括原型链上的属性)
    if (obj.hasOwnProperty(key)) {
      copy[key] = obj[key];
    }
  }

  return copy;
}

// 测试
const testObj = { a: 1, b: { c: 2 } };
const copied = shallowCopy(testObj);
console.log(copied.b === testObj.b); // true - 浅拷贝
方法4: 使用 Object.create() (原型链拷贝)
javascript 复制代码
// 这种方法会保持原型链
function shallowCopyWithPrototype(obj) {
  if (obj === null || typeof obj !== "object") {
    return obj;
  }

  // 创建一个新对象,继承原对象的原型
  const copy = Object.create(Object.getPrototypeOf(obj));

  // 拷贝自有属性
  Object.getOwnPropertyNames(obj).forEach((prop) => {
    const descriptor = Object.getOwnPropertyDescriptor(obj, prop);
    Object.defineProperty(copy, prop, descriptor);
  });

  return copy;
}

// 测试
const protoObj = { inherited: "from prototype" };
const objWithProto = Object.create(protoObj);
objWithProto.own = "own prototype";

const copiedWithProto = shallowCopyWithPrototype(objWithProto);
console.log(copiedWithProto.inherited); // from prototype - 继承原型
console.log(copiedWithProto.own); // own prototype
2.2 数组浅拷贝方法
方法1: 展开运算符
javascript 复制代码
const originalArray = [1, 2, 3, { x: 4 }];
const shallowArray = [...originalArray];

// 修改基本类型不会影响原数组
shallowArray[0] = 100;
console.log(originalArray[0]); // 1 - 不受影响

// 修改引用类型会影响原数组
shallowArray[3].x = 400;
console.log(originalArray[3].x); // 400 - 受影响!
方法2: slice()方法
javascript 复制代码
const shallowArray2 = originalArray.slice();
// 效果与展开运算符相同
方法3: concat()方法
javascript 复制代码
const shallowArray3 = originalArray.concat();
// 效果与展开运算符相同
方法4: Array.from() - ES6
javascript 复制代码
const shallowArray4 = Array.from(originalArray);
方法5: 手动实现数组浅拷贝
javascript 复制代码
function shallowCopyArray(arr) {
  if (!Array.isArray(arr)) {
    throw new TypeError("Excepted an array");
  }

  const copy = new Array(arr.length);
  for (let i = 0; i < arr.length; i++) {
    copy[i] = arr[i];
  }
  return copy;
}
2.3 浅拷贝的局限性

浅拷贝的主要问题是:

  1. 嵌套对象问题: 只拷贝一层,嵌套的对象仍然是共享的
  2. 对象共享问题: 修改浅拷贝对象的引用类型属性会影响原对象
  3. 特殊对象的引用共享: 对于Date、RegExp等特殊对象, 浅拷贝只复制引用, 不会创建新实例

三、深拷贝(Deep Copy)

深拷贝会创建一个完全独立的新对象, 递归复制所有嵌套的对象和数组, 使新对象与原对象完全分离。

3.1 使用 JSON 方法(最简单但有限制)
javascript 复制代码
const deepCopyWithJSON = JSON.parse(JSON.stringify(original));

// 测试
const obj = {
  name: "John",
  date: new Date(),
  func: () => console.log("hello"),
  undef: undefined,
  symbol: Symbol("id"),
  infinity: Infinity,
  nan: NaN,
  regex: /pattern/gi,
  set: new Set([1, 2, 3]),
  map: new Map([["key", "value"]]),
  nested: { a: 1 },
};

const jsonCopy = JSON.parse(JSON.stringify(obj));
console.log(jsonCopy);
// 输出:
// {
//   name: 'John',
//   date: '2025-12-04T14:13:18.238Z', // Date 变成了字符串
//   func 不存在 // 函数被省略
//   undef 不存在 // undefined 被省略
//   symbol 不存在 // Symbol 被省略
//   infinity: null, // Infinity 变成了 null
//   nan: null, // NaN 变成了 null
//   regex: {}, // RegExp 变成了空对象
//   set: {}, // Set 变成了空对象
//   map: {}, // Map 变成了空对象
//   nested: { a: 1 }
// }

JSON方法的限制:

  • 无法拷贝函数
  • 无法拷贝undefined
  • 无法拷贝Symbol
  • 无法拷贝循环引用
  • 特殊对象(Date、RegExp、Set、Map等)会被错误处理
  • 会忽略原型链
3.2 递归实现深拷贝

基础递归实现

javascript 复制代码
function deepClone(obj, hash = new WeakMap()) {
  // 处理基本类型和 null/undefined
  if (obj === null || typeof obj !== "object") {
    return obj;
  }

  // 处理 Date 对象
  if (obj instanceof Date) {
    return new Date(obj.getTime());
  }

  // 处理正则表达式
  if (obj instanceof RegExp) {
    return new RegExp(obj.source, obj.flags);
  }

  // 处理数组
  if (Array.isArray(obj)) {
    return obj.map((item) => deepClone(item, hash));
  }

  // 处理普通对象 - 检查循环引用
  if (hash.has(obj)) {
    return hash.get(obj);
  }

  const clone = {};
  hash.set(obj, clone);

  // 拷贝所有属性, 包括不可枚举属性(可选)
  const props = Object.getOwnPropertyNames(obj);
  for (const prop of props) {
    clone[prop] = deepClone(obj[prop], hash);
  }

  return clone;
}

// 测试基础功能
const testObj = {
  name: "Test",
  date: new Date(),
  regex: /test/gi,
  arr: [1, 2, { nested: true }],
  nested: {
    level1: {
      level2: "deep",
    },
  },
};

const cloned = deepClone(testObj);
console.log(cloned.date instanceof Date); // true
console.log(cloned.regex instanceof RegExp); // true
console.log(cloned.arr[2] === testObj.arr[2]); // false - 深拷贝成功

支持更多数据类型的完整实现

javascript 复制代码
function deepCloneComplete(target, map = new WeakMap()) {
  // 基本类型直接返回
  if (target === null || typeof target !== "object") {
    return target;
  }

  // 检查循环引用
  if (map.has(target)) {
    return map.get(target);
  }

  // 克隆特殊对象类型
  // Date
  if (target instanceof Date) {
    return new Date(target);
  }

  // RegExp
  if (target instanceof RegExp) {
    return new RegExp(target.source, target.flags);
  }

  // Map
  if (target instanceof Map) {
    const clone = new Map();
    map.set(target, clone);
    target.forEach((value, key) => {
      clone.set(deepCloneComplete(key, map), deepCloneComplete(value, map));
    });
    return clone;
  }

  // Set
  if (target instanceof Set) {
    const clone = new Set();
    map.set(target, clone);
    target.forEach((value) => {
      clone.add(deepCloneComplete(value, map));
    });
    return clone;
  }

  // ArrayBuffer
  if (target instanceof ArrayBuffer) {
    return target.slice(0);
  }

  // TypedArrat (Int8Array, Uint8Array, etc.)
  if (ArrayBuffer.isView(target)) {
    return new target.constructor(target);
  }

  // Array
  if (Array.isArray(target)) {
    const arrClone = [];
    map.set(target, arrClone);
    for (let i = 0; i < target.length; i++) {
      arrClone[i] = deepCloneComplete(target[i], map);
    }
    return arrClone;
  }

  // 普通对象
  const objClone = Object.create(Object.getPrototypeOf(target));
  map.set(target, objClone);

  // 获取所有属性(包括 Symbol)
  const allKeys = Reflect.ownKeys(target);

  for (const key of allKeys) {
    const descriptor = Object.getOwnPropertyDescriptor(target, key);

    if (descriptor) {
      if (descriptor.hasOwnProperty("value")) {
        // 数据属性
        objClone[key] = deepCloneComplete(target[key], map);
      } else {
        // 访问器属性 (getter/setter)
        Object.defineProperty(objClone, key, descriptor);
      }
    }
  }

  return objClone;
}

// 测试完整功能
const complexObj = {
  string: "hello",
  number: 42,
  boolean: true,
  null: null,
  undefined: undefined,
  symbol: Symbol("test"),
  date: new Date(),
  regex: /test/gi,
  array: [1, 2, { nested: true }],
  map: new Map([["key", { value: "map value" }]]),
  set: new Set([1, 2, 3]),
  buffer: new ArrayBuffer(8),
  uintArray: new Uint8Array([1, 2, 3]),
  object: {
    nested: {
      deeply: "nested value",
    },
  },
  get computed() {
    return this.string.toUpperCase();
  },
  method() {
    return this.string;
  },
};

// 添加循环引用
complexObj.self = complexObj;
complexObj.circular = { parent: complexObj };

const completeClone = deepCloneComplete(complexObj);

console.log(completeClone.date instanceof Date); // true
console.log(completeClone.regex instanceof RegExp); // true
console.log(completeClone.map instanceof Map); // true
console.log(completeClone.uintArray instanceof Uint8Array); // true
console.log(completeClone.computed); // 'HELLO'
console.log(completeClone.method()); // 'hello'
console.log(completeClone.self === completeClone); // true - 循环引用正确处理
console.log(completeClone.circular.parent === completeClone); // true
3.3 使用 structuredClone API(现代浏览器)

HTML5规范引入了structuredClone()方法, 提供了一种标准化的深拷贝方法。

javascript 复制代码
// 浏览器环境中的使用
const original = {
    name: 'John',
    date: new Date(),
    array: [1, 2, 3],
    nested: { value: 'test' }
};

try {
    const cloned = structuredClone(original);
    console.log(cloned.date instanceof Date); // true
    console.log(cloned.nested === original.nested); // false
} catch (err) {
    console.log('structuredClone not supported:', err);
}

// Node.js 中的使用(v17+)
if (typeof structuredClone === 'function') {
    const cloned = structuredClone(original);
}

// structuredClone 支持的数据类型:
// - 基本类型(除 Symbol)
// - Boolean、Number、String 对象
// - Date
// - RegExp
// - ArrayBuffer、TypedArray
// - Map、Set
// - Array、Object
// - 循环引用

// 不支持:
// - 函数
// - DOM 节点
// - Error 对象
// - 原型链
3.4 使用第三方库

对于生产环境, 使用成熟的第三方库通常是更好的选择: Lodash_.cloneDeep

javascript 复制代码
// 使用 Lodash
const _ = require('lodash');

const obj = {
    date: new Date(),
    regex: /test/gi,
    func: () => console.log('hi'),
    nested: { a: 1 }
};

const cloned = _.cloneDeep(obj);
console.log(cloned.date instanceof Date); // true
console.log(cloned.regex instanceof RegExp); // true
console.log(typeof cloned.func); // 'function' - 函数被保留

自己实现类似LodashcloneDeep

javascript 复制代码
function cloneDeep(value, stack = new Map()) {
    // 基本类型直接返回
    if (value === null || typeof value !== 'object') {
        return value;
    }
    
    // 检查循环引用
    if (stack.has(value)) {
        return stack.get(value);
    }
    
    let clone;
    
    // 处理特殊对象
    if (value instanceof Date) {
        clone = new Date(value.getTime());
        stack.set(value, clone);
        return clone;
    }
    
    if (value instanceof RegExp) {
        clone = new RegExp(value.source, value.flags);
        stack.set(value, clone);
        return clone;
    }
    
    if (value instanceof Map) {
        clone = new Map();
        stack.set(value, clone);
        value.forEach((val, key) => {
            clone.set(cloneDeep(key, stack), cloneDeep(val, stack));
        });
        return clone;
    }
    
    if (value instanceof Set) {
        clone = new Set();
        stack.set(value, clone);
        value.forEach(val => {
            clone.add(cloneDeep(val, stack));
        });
        return clone;
    }
    
    if (Array.isArray(value)) {
        clone = [];
        stack.set(value, clone);
        for (let i = 0; i < value.length; i++) {
            clone[i] = cloneDeep(value[i], stack);
        }
        return clone;
    }
    
    // 处理普通对象
    clone = Object.create(Object.getPrototypeOf(value));
    stack.set(value, clone);
    
    // 拷贝所有属性
    for (const key in value) {
        if (value.hasOwnProperty(key)) {
            clone[key] = cloneDeep(value[key], stack);
        }
    }
    
    return clone;
}

四、特殊场景和边缘情况

4.1 循环引用处理

循环引用是深拷贝中最棘手的问题之一, 处理不当会导致无限递归和栈溢出

javascript 复制代码
// 循环引用示例
const circularObj = { name: 'Circular' };
circularObj.self = circularObj;
circularObj.ref = { parent: circularObj };

// 处理循环引用的深拷贝实现
function cloneDeepWithCircular(obj, cache = new WeakMap()) {
    // 非对象直接返回
    if (obj === null || typeof obj !== 'object') {
        return obj;
    }
    
    // 检查缓存中是否已有该对象的拷贝
    if (cache.has(obj)) {
        return cache.get(obj);
    }
    
    // 根据对象类型创建相应的空结构
    let clone;
    if (obj instanceof Date) {
        clone = new Date(obj);
    } else if (obj instanceof RegExp) {
        clone = new RegExp(obj.source, obj.flags);
    } else if (obj instanceof Map) {
        clone = new Map();
    } else if (obj instanceof Set) {
        clone = new Set();
    } else if (Array.isArray(obj)) {
        clone = [];
    } else {
        clone = Object.create(Object.getPrototypeOf(obj));
    }
    
    // 将空结构存入缓存(在递归前存入,防止无限递归)
    cache.set(obj, clone);
    
    // 递归拷贝
    if (obj instanceof Map) {
        obj.forEach((value, key) => {
            clone.set(
                cloneDeepWithCircular(key, cache),
                cloneDeepWithCircular(value, cache)
            );
        });
    } else if (obj instanceof Set) {
        obj.forEach(value => {
            clone.add(cloneDeepWithCircular(value, cache));
        });
    } else if (Array.isArray(obj)) {
        for (let i = 0; i < obj.length; i++) {
            clone[i] = cloneDeepWithCircular(obj[i], cache);
        }
    } else {
        for (const key in obj) {
            if (obj.hasOwnProperty(key)) {
                clone[key] = cloneDeepWithCircular(obj[key], cache);
            }
        }
    }
    
    return clone;
}

// 测试循环引用
const testCircular = { a: 1 };
testCircular.b = testCircular;
testCircular.c = { ref: testCircular };

const clonedCircular = cloneDeepWithCircular(testCircular);
console.log(clonedCircular.b === clonedCircular); // true
console.log(clonedCircular.c.ref === clonedCircular); // true
console.log(clonedCircular !== testCircular); // true - 不是同一个对象
4.2 函数拷贝

函数拷贝是一个有争议的话题,因为函数可能依赖于闭包中的外部变量。

javascript 复制代码
// 函数拷贝的几种方法
function cloneFunction(func) {
    // 方法1:使用 eval(不推荐,安全问题)
    const funcString = func.toString();
    
    // 方法2:使用 Function 构造函数
    const clonedFunc = new Function('return ' + funcString)();
    
    // 拷贝函数属性
    Object.getOwnPropertyNames(func).forEach(prop => {
        if (prop !== 'length' && prop !== 'name' && prop !== 'prototype') {
            Object.defineProperty(clonedFunc, prop, 
                Object.getOwnPropertyDescriptor(func, prop));
        }
    });
    
    // 拷贝原型
    clonedFunc.prototype = func.prototype;
    
    return clonedFunc;
}

// 实际使用中,通常不拷贝函数,而是保留引用
function cloneDeepWithFunction(obj, cache = new Map()) {
    if (typeof obj === 'function') {
        return obj; // 直接返回函数引用
    }
    
    // ... 其他类型的处理
}

// 测试
const objWithFunc = {
    name: 'Test',
    sayHello: function() {
        console.log(`Hello, ${this.name}`);
    },
    arrowFunc: () => console.log('Arrow')
};

const clonedWithFunc = cloneDeepWithFunction(objWithFunc);
clonedWithFunc.name = 'Cloned';
clonedWithFunc.sayHello(); // Hello, Cloned
4.3 DOM元素拷贝

DOM元素有特殊的拷贝需求:

javascript 复制代码
function cloneDOMElement(element, deep = true) {
    // 使用 cloneNode 方法
    const cloned = element.cloneNode(deep);
    
    // 处理事件监听器
    // 注意:cloneNode 不会拷贝事件监听器
    
    // 处理数据属性
    if (element.dataset) {
        Object.assign(cloned.dataset, element.dataset);
    }
    
    // 处理自定义属性
    const attributes = element.attributes;
    for (let i = 0; i < attributes.length; i++) {
        const attr = attributes[i];
        if (attr.name.startsWith('data-') || attr.name.startsWith('aria-')) {
            cloned.setAttribute(attr.name, attr.value);
        }
    }
    
    return cloned;
}

// 使用示例
// const originalDiv = document.getElementById('original');
// const clonedDiv = cloneDOMElement(originalDiv, true);
// document.body.appendChild(clonedDiv);
4.4 性能优化技巧

深拷贝可能成为性能瓶颈,特别是处理大型对象时。

javascript 复制代码
// 性能优化的深拷贝
function fastDeepClone(obj, cache = new WeakMap()) {
    // 快速路径:基本类型
    if (obj === null || typeof obj !== 'object') {
        return obj;
    }
    
    // 检查缓存
    if (cache.has(obj)) {
        return cache.get(obj);
    }
    
    let clone;
    
    // 使用构造函数快速创建对象
    const Ctor = obj.constructor;
    
    switch (Ctor) {
        case Date:
            clone = new Date(obj);
            break;
        case RegExp:
            clone = new RegExp(obj);
            break;
        case Map:
            clone = new Map();
            cache.set(obj, clone);
            obj.forEach((value, key) => {
                clone.set(fastDeepClone(key, cache), fastDeepClone(value, cache));
            });
            return clone;
        case Set:
            clone = new Set();
            cache.set(obj, clone);
            obj.forEach(value => {
                clone.add(fastDeepClone(value, cache));
            });
            return clone;
        case Array:
            clone = new Array(obj.length);
            cache.set(obj, clone);
            for (let i = 0; i < obj.length; i++) {
                clone[i] = fastDeepClone(obj[i], cache);
            }
            return clone;
        default:
            // 普通对象
            clone = Object.create(Object.getPrototypeOf(obj));
            cache.set(obj, clone);
    }
    
    // 快速属性拷贝
    const keys = Object.keys(obj);
    for (let i = 0; i < keys.length; i++) {
        const key = keys[i];
        clone[key] = fastDeepClone(obj[key], cache);
    }
    
    return clone;
}

// 性能对比
const largeObj = {};
for (let i = 0; i < 10000; i++) {
    largeObj[`key${i}`] = {
        nested: { value: i },
        array: new Array(10).fill(i)
    };
}

console.time('JSON 深拷贝');
JSON.parse(JSON.stringify(largeObj));
console.timeEnd('JSON 深拷贝');

console.time('递归深拷贝');
fastDeepClone(largeObj);
console.timeEnd('递归深拷贝');

五、实践应用和最佳实践

5.1 何时使用浅拷贝

适合浅拷贝的场景:

  1. 简单数据结构: 对象只有一层,没有嵌套
  2. 性能敏感: 需要快速拷贝,不关心嵌套对象的独立性
  3. 不可变数据: 数据不会被修改,或修改时创建新对象
  4. 配置对象: 只需要修改顶层配置
javascript 复制代码
// 浅拷贝适用场景
const config = {
    apiUrl: 'https://api.example.com',
    timeout: 5000,
    headers: {
        'Content-Type': 'application/json'
    }
};

// 只需要修改顶层配置时
const devConfig = { ...config, apiUrl: 'https://dev-api.example.com' };
// headers 对象仍然是共享的,但这通常是可以接受的
5.2 何时使用深拷贝

适合深拷贝的场景:

  1. 复杂嵌套结构: 对象有多层嵌套,需要完全独立
  2. 状态管理:ReduxVuex中修改状态时
  3. 不可变更新: 函数式编程中创建新状态
  4. 数据隔离: 防止原始数据被意外修改
  5. 缓存数据: 保存数据快照
javascript 复制代码
// 深拷贝适用场景 - Redux reducer
function todoReducer(state = initialState, action) {
    switch (action.type) {
        case 'ADD_TODO':
            // 需要深拷贝来创建新状态
            return {
                ...state,
                todos: [
                    ...state.todos,
                    {
                        id: action.id,
                        text: action.text,
                        completed: false
                    }
                ]
            };
        case 'TOGGLE_TODO':
            // 深度更新嵌套对象
            return {
                ...state,
                todos: state.todos.map(todo =>
                    todo.id === action.id
                        ? { ...todo, completed: !todo.completed }
                        : todo
                )
            };
        default:
            return state;
    }
}
5.3 性能优化策略
  1. 按需拷贝: 只拷贝需要修改的部分
  2. 结构共享: 使用不可变数据结构库(如Immutable.js)
  3. 增量更新: 只更新变化的部分
  4. 缓存结果: 对于相同输入返回缓存的结果
javascript 复制代码
// 按需拷贝示例
function updateUser(user, updates) {
    // 只深拷贝需要修改的部分
    const updatedUser = { ...user };
    
    if (updates.address) {
        updatedUser.address = { ...user.address, ...updates.address };
    }
    
    if (updates.preferences) {
        updatedUser.preferences = { ...user.preferences, ...updates.preferences };
    }
    
    // 其他属性直接浅拷贝
    Object.keys(updates).forEach(key => {
        if (key !== 'address' && key !== 'preferences') {
            updatedUser[key] = updates[key];
        }
    });
    
    return updatedUser;
}

// 使用代理实现惰性拷贝
function createLazyCopy(original) {
    const changes = new Map();
    const handler = {
        get(target, prop) {
            // 如果该属性有修改,返回修改后的值
            if (changes.has(prop)) {
                return changes.get(prop);
            }
            
            // 否则返回原始值
            const value = target[prop];
            
            // 如果是对象,则返回代理
            if (value && typeof value === 'object') {
                return new Proxy(value, handler);
            }
            
            return value;
        },
        set(target, prop, value) {
            // 记录修改
            changes.set(prop, value);
            return true;
        }
    };
    
    return new Proxy(original, handler);
}
5.4 安全性考虑
  1. 避免原型污染: 确保不会拷贝__proto__等特殊属性
  2. 防止恶意对象: 处理具有getter的执行可能引发副作用的对象
  3. 内存安全: 避免拷贝会导致内存泄漏的大型对象
javascript 复制代码
// 安全的深拷贝实现
function safeDeepClone(obj, options = {}) {
    const {
        maxDepth = 100,
        maxSize = 10000,
        allowFunctions = false,
        allowSymbols = true
    } = options;
    
    let size = 0;
    
    function clone(current, depth, cache) {
        // 检查深度限制
        if (depth > maxDepth) {
            throw new Error('Maximum depth exceeded');
        }
        
        // 检查大小限制
        if (size > maxSize) {
            throw new Error('Maximum size exceeded');
        }
        
        // 基本类型处理
        if (current === null || typeof current !== 'object') {
            // 检查 Symbol
            if (typeof current === 'symbol' && !allowSymbols) {
                throw new Error('Symbols are not allowed');
            }
            return current;
        }
        
        // 检查函数
        if (typeof current === 'function') {
            if (!allowFunctions) {
                throw new Error('Functions are not allowed');
            }
            return current;
        }
        
        // 检查缓存(防止循环引用)
        if (cache.has(current)) {
            return cache.get(current);
        }
        
        // 根据类型创建空对象
        let clone;
        const Ctor = current.constructor;
        
        switch (Ctor) {
            case Date:
                clone = new Date(current);
                break;
            case RegExp:
                clone = new RegExp(current);
                break;
            case Map:
                clone = new Map();
                break;
            case Set:
                clone = new Set();
                break;
            case Array:
                clone = [];
                break;
            default:
                // 普通对象 - 避免原型污染
                clone = Object.create(null);
        }
        
        // 存入缓存
        cache.set(current, clone);
        
        // 递归拷贝
        if (current instanceof Map) {
            current.forEach((value, key) => {
                size++;
                clone.set(
                    clone(key, depth + 1, cache),
                    clone(value, depth + 1, cache)
                );
            });
        } else if (current instanceof Set) {
            current.forEach(value => {
                size++;
                clone.add(clone(value, depth + 1, cache));
            });
        } else if (Array.isArray(current)) {
            for (let i = 0; i < current.length; i++) {
                size++;
                clone[i] = clone(current[i], depth + 1, cache);
            }
        } else {
            for (const key in current) {
                // 避免拷贝原型链上的属性
                if (current.hasOwnProperty(key)) {
                    // 避免特殊属性
                    if (key === '__proto__' || key === 'constructor' || key === 'prototype') {
                        continue;
                    }
                    
                    size++;
                    clone[key] = clone(current[key], depth + 1, cache);
                }
            }
        }
        
        return clone;
    }
    
    return clone(obj, 0, new WeakMap());
}

六、现在JavaScript中的拷贝模式

6.1 不可变数据模式
javascript 复制代码
// 使用 Object.freeze 实现浅不可变
const immutableConfig = Object.freeze({
    apiUrl: 'https://api.example.com',
    timeout: 5000,
    headers: Object.freeze({
        'Content-Type': 'application/json'
    })
});

// 深度冻结
function deepFreeze(obj) {
    Object.freeze(obj);
    
    Object.getOwnPropertyNames(obj).forEach(prop => {
        const value = obj[prop];
        
        if (value && typeof value === 'object' && !Object.isFrozen(value)) {
            deepFreeze(value);
        }
    });
    
    return obj;
}

// 使用 Proxy 实现不可变接口
function createImmutable(obj) {
    const handler = {
        get(target, prop) {
            const value = target[prop];
            
            // 如果是对象,返回代理
            if (value && typeof value === 'object') {
                return createImmutable(value);
            }
            
            return value;
        },
        set() {
            throw new Error('Cannot modify immutable object');
        },
        deleteProperty() {
            throw new Error('Cannot delete property from immutable object');
        }
    };
    
    return new Proxy(obj, handler);
}
6.2 结构共享(Persistent Data Structures)
javascript 复制代码
// 简化的结构共享实现
class PersistentMap {
    constructor(data = {}) {
        this.data = data;
        this.version = 0;
    }
    
    set(key, value) {
        // 创建新版本,共享未修改的数据
        const newData = { ...this.data };
        newData[key] = value;
        
        const newMap = new PersistentMap(newData);
        newMap.version = this.version + 1;
        
        return newMap;
    }
    
    get(key) {
        return this.data[key];
    }
    
    // 比较两个版本是否相等
    equals(other) {
        if (this === other) return true;
        if (this.version !== other.version) return false;
        
        // 深度比较(简化版)
        return JSON.stringify(this.data) === JSON.stringify(other.data);
    }
}

// 使用示例
const map1 = new PersistentMap({ a: 1, b: 2 });
const map2 = map1.set('c', 3);
const map3 = map2.set('b', 20);

console.log(map1.get('b')); // 2
console.log(map3.get('b')); // 20
console.log(map1.data === map2.data); // false
6.3 使用现代API
javascript 复制代码
// 使用 Object.groupBy 和 Map (ES2024)
const users = [
    { id: 1, name: 'Alice', group: 'admin' },
    { id: 2, name: 'Bob', group: 'user' },
    { id: 3, name: 'Charlie', group: 'admin' }
];

// 分组并创建不可变结构
const grouped = Object.groupBy(users, user => user.group);

// 转换为不可变 Map
const immutableGroups = new Map(Object.entries(grouped));

// 深度冻结
function deepFreezeMap(map) {
    map.forEach(value => {
        if (value && typeof value === 'object') {
            deepFreeze(value);
        }
    });
    Object.freeze(map);
}

deepFreezeMap(immutableGroups);

// 创建新版本
const updatedGroups = new Map(immutableGroups);
updatedGroups.set('moderator', [{ id: 4, name: 'David', group: 'moderator' }]);

七、总结与最佳实践建议

7.1 拷贝方法选择指南
场景 推荐方法 理由
简单对象,无嵌套 浅拷贝({...obj}) 快速、简单、高效
配置对象,少量嵌套 浅拷贝+手动处理嵌套 平衡性能和正确性
复杂嵌套对象 深拷贝(递归或sutrcturedClone) 确保完全独立
包含特殊类型(Date、RegExp) 自定义深拷贝或Lodash 正确处理特殊对象
性能关键路径 按需拷贝+结构共享 最大化性能
不可变数据 深拷贝+Object.freeze 确保数据不可变
生产环境 Lodash的_.cloneDeep 成熟、稳定、功能全
7.2 黄金法则
  1. 明确需求: 先确定是否需要深拷贝,很多时候浅拷贝就足够了
  2. 测试边界情况: 总是测试循环引用、特殊对象和大型数据结构
  3. 考虑性能: 对于频繁操作的数据,考虑使用不可变数据结构
  4. 保持简洁: 避免过度复杂的拷贝逻辑,必要时使用成熟的库
  5. 安全性第一: 处理用户输入时要特别小心,避免原型污染和其他安全问题
7.3 未来趋势
  1. 结构化克隆 API: structuredClone()将成为深拷贝的标准方式
  2. Records和Tuples: ES提案,提供原生不可变数据结构
  3. 更快的拷贝算法: WebAssembly和新的 JavaScript 引擎优化
  4. 编译时优化: 通过静态分析优化拷贝操作
7.4 最终建议代码
javascript 复制代码
// 生产环境推荐的拷贝工具函数
class CloneUtils {
    // 简单的深拷贝(适合大多数场景)
    static deepClone(obj) {
        // 优先使用原生 API
        if (typeof structuredClone === 'function') {
            try {
                return structuredClone(obj);
            } catch (e) {
                // 如果失败,回退到其他方法
            }
        }
        
        // 回退到 JSON 方法(有限制)
        try {
            return JSON.parse(JSON.stringify(obj));
        } catch (e) {
            // 如果 JSON 方法失败,使用自定义实现
            return this.customDeepClone(obj);
        }
    }
    
    // 自定义深拷贝实现
    static customDeepClone(obj, cache = new WeakMap()) {
        // 基础类型和函数
        if (obj === null || typeof obj !== 'object') {
            return obj;
        }
        
        // 检查缓存
        if (cache.has(obj)) {
            return cache.get(obj);
        }
        
        // 处理特殊对象
        if (obj instanceof Date) {
            const cloned = new Date(obj);
            cache.set(obj, cloned);
            return cloned;
        }
        
        if (obj instanceof RegExp) {
            const cloned = new RegExp(obj);
            cache.set(obj, cloned);
            return cloned;
        }
        
        // 创建空对象/数组
        const cloned = Array.isArray(obj) ? [] : {};
        cache.set(obj, cloned);
        
        // 递归拷贝属性
        for (const key in obj) {
            if (obj.hasOwnProperty(key)) {
                cloned[key] = this.customDeepClone(obj[key], cache);
            }
        }
        
        return cloned;
    }
    
    // 安全的浅拷贝(防止原型污染)
    static safeShallowClone(obj) {
        if (obj === null || typeof obj !== 'object') {
            return obj;
        }
        
        const cloned = Array.isArray(obj) ? [] : {};
        
        for (const key in obj) {
            if (obj.hasOwnProperty(key) && 
                key !== '__proto__' && 
                key !== 'constructor' && 
                key !== 'prototype') {
                cloned[key] = obj[key];
            }
        }
        
        return cloned;
    }
    
    // 性能优化的拷贝(只拷贝需要修改的部分)
    static smartClone(original, modifications) {
        const result = { ...original };
        
        for (const key in modifications) {
            if (modifications.hasOwnProperty(key)) {
                const originalValue = original[key];
                const modifiedValue = modifications[key];
                
                if (originalValue && typeof originalValue === 'object' &&
                    modifiedValue && typeof modifiedValue === 'object' &&
                    !Array.isArray(originalValue) && !Array.isArray(modifiedValue)) {
                    // 递归处理嵌套对象
                    result[key] = this.smartClone(originalValue, modifiedValue);
                } else {
                    result[key] = modifiedValue;
                }
            }
        }
        
        return result;
    }
}

// 使用示例
const data = {
    user: {
        name: 'John',
        settings: {
            theme: 'dark',
            notifications: true
        }
    },
    items: [1, 2, 3]
};

// 简单深拷贝
const cloned1 = CloneUtils.deepClone(data);

// 智能拷贝(只修改部分)
const cloned2 = CloneUtils.smartClone(data, {
    user: {
        settings: {
            theme: 'light'
        }
    }
});

console.log(cloned2.user.settings.theme); // 'light'
console.log(cloned2.user.settings.notifications); // true(保持原值)
console.log(cloned2.items === data.items); // true(未修改的部分共享引用)

结语

JavaScript拷贝是一个看似简单实则复杂的话题。通过本文的学习,你应该能够:

  1. 理解浅拷贝和深拷贝的根本区别
  2. 根据不同的场景选择合适的拷贝策略
  3. 实现各种拷贝方法,处理边界情况
  4. 优化拷贝性能,避免常见陷阱 记住,没有一种拷贝方法是适用于所有场景的万能解决方案。最好的方法是理解每种技术的优缺点,根据具体需求做出明智的选择。

在实际开发中,当面临拷贝需求时,先问自己几个问题:

  • 我真的需要完全独立的数据吗?
  • 数据结构有多复杂?
  • 性能要求有多高?
  • 是否有特殊类型的对象需要处理?

通过回答这些问题,你将能够选择最合适的拷贝策略,写出更健壮、更高效的代码。

深入学习资源:

相关推荐
欧阳天风1 小时前
js实现鼠标横向滚动
开发语言·前端·javascript
局i1 小时前
Vue 指令详解:v-for、v-if、v-show 与 {{}} 的妙用
前端·javascript·vue.js
码界奇点2 小时前
Java Web学习 第15篇jQuery从入门到精通的万字深度解析
java·前端·学习·jquery
小鑫同学2 小时前
Alias Assistant:新一代 macOS Shell 别名管理解决方案
前端·前端工程化
꒰ঌ小武໒꒱2 小时前
RuoYi-Vue 前端环境搭建与部署完整教程
前端·javascript·vue.js·nginx
名字越长技术越强2 小时前
前端之相对路径
前端
望道同学3 小时前
PMP/信息系统项目管理师 9 张 思维导图【考试必备】
前端·后端·程序员
局i4 小时前
Vue 中 v-text 与 v-html 的区别:文本渲染与 HTML 解析的抉择
前端·javascript·vue.js
fruge4 小时前
接口 Mock 工具对比:Mock.js、Easy Mock、Apifox 的使用场景与配置
开发语言·javascript·ecmascript