JavaScript基础:深克隆和浅克隆
JavaScript基础:深克隆和浅克隆

克隆对象是常见的操作,尤其是在处理复杂数据结构时。为了避免对原始对象的直接修改,通常会选择对对象进行“克隆”操作。详细讨论深克隆(Deep Clone)和浅克隆(Shallow Clone)的概念、区别、实现方式及应用场景。

什么是浅克隆?

浅克隆(Shallow Clone)是指只复制对象的第一层属性。如果对象的某个属性是引用类型(如对象或数组),那么复制的只是这个引用类型的地址,而不是其实际内容。因此,修改克隆后的对象中的引用类型属性时,原始对象的该属性也会受到影响。

浅克隆的实现方法

浅克隆通常通过一些 JavaScript 内置方法来实现,最常见的有以下几种:

  1. Object.assign()

    Object.assign() 可以将一个或多个源对象的属性复制到目标对象。它只会进行浅克隆。

    const obj1 = { a: 1, b: { c: 2 } };
    const obj2 = Object.assign({}, obj1);
    
    obj2.b.c = 3;
    console.log(obj1.b.c); // 输出 3,obj1 也受到了影响
    
  2. 扩展运算符(...)

    ES6 提供的扩展运算符可以方便地实现浅克隆,尤其适用于数组和对象。

    const obj1 = { a: 1, b: { c: 2 } };
    const obj2 = { ...obj1 };
    
    obj2.b.c = 3;
    console.log(obj1.b.c); // 输出 3,obj1 也受到了影响
    
浅克隆的局限性

由于浅克隆只复制对象的第一层,对于多层嵌套的对象,浅克隆无法避免引用类型的共享。例如:

const obj1 = {
  a: 1,
  b: {
    c: 2,
    d: { e: 3 }
  }
};

const obj2 = { ...obj1 };
obj2.b.d.e = 4;

console.log(obj1.b.d.e); // 输出 4,原始对象的深层属性也被修改

浅克隆仅适用于平面数据结构或对引用类型属性修改无关紧要的场景。

什么是深克隆?

深克隆(Deep Clone)是指完全复制对象及其所有嵌套的引用类型,确保克隆后的对象与原始对象彻底分离。即使对象内部有多层嵌套的引用类型,它们也会被重新创建,从而不会影响原始对象。

深克隆的实现方法

实现深克隆的方法比浅克隆更复杂。以下是一些常见的深克隆实现方式:

  1. JSON.parse() 和 JSON.stringify()

    最简单的深克隆方式是使用 JSON.parse()JSON.stringify()。该方法将对象先转换为 JSON 字符串,再将字符串解析为新对象。

    const obj1 = { a: 1, b: { c: 2 } };
    const obj2 = JSON.parse(JSON.stringify(obj1));
    
    obj2.b.c = 3;
    console.log(obj1.b.c); // 输出 2,obj1 未受影响
    

    虽然这种方法简单,但它存在一些局限性:

    • 不能克隆 undefined、函数、Symbol
    • 会丢失原型链。
    • 不能正确处理 DateRegExp 等特殊对象类型。
  2. 递归实现深克隆

    为了克服 JSON.parse()JSON.stringify() 的局限性,我们可以手动编写递归函数来实现深克隆。这种方法能处理大多数复杂数据结构。

    function deepClone(obj) {
      if (obj === null || typeof obj !== 'object') {
        return obj;
      }
    
      const clone = Array.isArray(obj) ? [] : {};
    
      for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
          clone[key] = deepClone(obj[key]);
        }
      }
    
      return clone;
    }
    
    const obj1 = { a: 1, b: { c: 2, d: [1, 2] } };
    const obj2 = deepClone(obj1);
    
    obj2.b.c = 3;
    obj2.b.d[0] = 10;
    
    console.log(obj1.b.c); // 输出 2,obj1 未受影响
    console.log(obj1.b.d[0]); // 输出 1,obj1 未受影响
    
  3. 使用第三方库

    对于更复杂的深克隆需求,可以使用一些成熟的第三方库,如 Lodash 提供的 _.cloneDeep() 函数,它可以处理更复杂的数据结构。

    const _ = require('lodash');
    
    const obj1 = { a: 1, b: { c: 2 } };
    const obj2 = _.cloneDeep(obj1);
    
    obj2.b.c = 3;
    console.log(obj1.b.c); // 输出 2,obj1 未受影响
    

    Lodash 的深克隆功能强大,能够处理诸如 DateRegExp、循环引用等复杂情况。

深克隆与浅克隆的对比
特性浅克隆深克隆
克隆层次仅第一层所有层级
复制引用类型复制引用类型的地址复制引用类型的值
性能更快较慢(特别是大对象时)
常用方法Object.assign()、扩展运算符递归函数、JSON.stringify()
应用场景适用于简单对象适用于复杂嵌套对象
应用场景
  1. 浅克隆的应用场景

    浅克隆适用于扁平数据结构或仅希望复制第一层数据的场景。例如,表单数据或配置文件通常只包含简单的对象或数组,此时浅克隆是个高效的选择。

    const config = { theme: 'light', version: 1.2 };
    const newConfig = { ...config, theme: 'dark' }; // 只改变第一层
    
  2. 深克隆的应用场景

    深克隆适用于多层嵌套对象,或者需要确保克隆后的对象完全独立于原始对象的场景。例如,在处理 Redux 状态更新、嵌套的 JSON 数据或复杂的 UI 组件树时,深克隆可以避免不必要的副作用。

    const state = {
      user: { name: 'Alice', settings: { theme: 'light' } }
    };
    const newState = deepClone(state);
    newState.user.settings.theme = 'dark';