前端深拷贝和浅拷贝

在前端开发中我们经常会对数据对象进行处理,并且不影响原对象,即遵循不可变值。因为可能其它地方会用到原始对象,这就引出了一个概念拷贝(对数据进行复制)。在前端拷贝分二种浅拷贝和深拷贝。

一、基本概念

1. 浅拷贝

浅拷贝只复制对象一层,改变一层对象的属性不会影响原对象,如果对象的属性一个引用类型的时候则复制的是引用地址,修改属性也会影响原来的属性。

2. 深拷贝

深拷贝会完全复整个对象,无论对象有几层,新对象和旧对象完全隔离,二者之间的修改互不影响。

二、浅拷贝的实现方式

浅拷贝是数组操作中最常用的技术,数组经常需要在不修改原数组的情况下进行各种变换和处理。

1. 扩展运算符(...)

const original = { a: 1, b: { c: 2 } };
const copy = { ...original };

original.a = 10;
original.b.c = 20;
console.log(copy.a); // 1 (基本类型不受影响)
console.log(copy.b.c); // 20 (引用类型受影响)

2. Object.assign()

const copy = Object.assign({}, original);

3. Array.prototype.slice() (数组)

arr.slice([begin[, end]])根据开始和结束索引选取内容并返回一个新数组。

  • begin(可选):起始索引(从0开始)
  • end(可选):结束索引(不包括该位置)
const originalArray = [{ name: 'Alice' }, { name: 'Bob' }];

// 使用 slice() 进行浅拷贝
const slicedArray = originalArray.slice(0,1);

// 改变原数组中对象的属性
originalArray[0].name = 'Charlie';

// 查看拷贝后的数组
console.log(slicedArray[0].name); 
// 输出: 'Charlie' 
// 为什么?因为 slicedArray[0] 仍然指向 originalArray[0] 同一个对象。

4.Array.prototype.concat()(数组)

Array.prototype.concat() 是 JavaScript 中常用的数组合并方法,它同样执行的是​​浅拷贝(shallow copy)。concat对二个数组进行合并并返回一个新数组。

const arr1 = [{ a: 1 }];
const arr2 = [{ b: 2 }];
const merged = arr1.concat(arr2);

arr1[0].a = 3;
arr1.push(4);
console.log(arr1);// [{ a: 3 }, 4]
console.log(merged); // [{ a: 3 }, { b: 2 }]

5. Array.from() (数组)

const original = [{ a: 1 }, { b: 2 }];
const copy = Array.from(original);

original[0].a = 3;
console.log(copy[0].a); // 3 (证明是浅拷贝)

三、深拷贝的实现方式

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

const original = {
    name: 'xx',
    age: 18,
    address: {
        city: 'Beijing',
        country: 'China'
    }
};
const str  = JSON.stringify(original);
const deepCopy = JSON.parse(str);
console.log('深拷贝对象', deepCopy);

缺点​​:

  • 不能处理函数、特殊对象、Symbol、undefined等
  • 会丢失原型链
  • 不能处理循环引用
const obj = {
  func: function() {},       // 函数 → 丢失
  symbol: Symbol('sym'),     // Symbol → 丢失
  undefined: undefined,      // undefined → 丢失
  date: new Date(),          // Date → 字符串
  regex: /pattern/g,         // RegExp → 空对象
  nan: NaN,                  // NaN → null
  infinity: Infinity,        // Infinity → null
  set: new Set([1, 2, 3]),    // Set → {}
  map: new Map([['a', 1]]),   // Map → {}
  blob: new Blob(),           // Blob → {}
};

2. 手写递归实现

const obj = {
    name: 'xx',
    age: "12",
    child: {
        username: "旧对象"
    }
};

function deepCopy(obj) {
    if (typeof (obj) == 'object' && obj !== null) {
        const res = obj instanceof Array ? [] : {}
        for (const item in obj) {
            //保证不是从原型上继承来的属性,是对象自身的属性
            if (obj.hasOwnProperty(item)) {
                res[item] = deepCopy(obj[item])
            }
        }
        return res;
    } else {
        return obj
    }
}

const newObj = deepCopy(obj);
newObj.child.username = '新对象'
console.log('obj---', obj.child.username)
console.log('newObj---', newObj.child.username)

3. 使用structuredClone方法

JavaScript 在较新版本(ECMAScript 2021+)中新增了一个原生深拷贝 API:​​structuredClone()​​。这是一个内置的全局方法,可以轻松实现深拷贝,且无需依赖第三方库。

const original = {
  name: "Alice",
  address: { city: "Paris" },
  hobbies: ["coding", "music"],
  self: null // 用于循环引用
};

original.self = original; // 添加循环引用

// 深拷贝
const copy = structuredClone(original);

// 验证深拷贝
copy.address.city = "Berlin"; 
console.log(original.address.city); // "Paris"(未改变原对象)

// 验证循环引用
console.log(copy.self === copy); // true(正确保留循环引用)

 核心特性

  1. ​​深层拷贝​​

    完全复制嵌套对象、数组、Map、Set 等复杂结构(非引用拷贝)。2.

  2. ​​支持循环引用​​

    自动处理对象的循环引用(如 obj.self = obj)。3.

  3. ​​安全的数据类型支持​​

    兼容多数常见类型:

    ✅ ObjectArray

    ✅ MapSetDateRegExp

    ✅ ArrayBufferBlobImageData等(需环境支持)

    ❌ 不支持 ​​函数​​、DOM 节点、原型链(会抛出错误)

4. 使用第三方库

  • Lodash的_.cloneDeep()
const _ = require('lodash');
const deepCopy = _.cloneDeep(original);

四、浅拷贝适用场景

1. 性能敏感操作

  • 当处理大型对象且只需要第一层属性时。
  • 数据是数组并且只有一层可以优先考虑使用数组提供的方法进行浅拷贝。

2. 明确知道只需修改顶层属性

3. 不可变数据模式中的简单更新

  • 比如react的状态赋值操作
    function TodoList() {
      const [todos, setTodos] = useState([{text: 'Learn React'}]);
    
      // 添加todo - 浅拷贝足够
      const addTodo = text => {
        setTodos(prev => [...prev, {text}]);
      };
    
      // 切换完成状态 - 需要元素级浅拷贝
      const toggleTodo = index => {
        setTodos(prev => prev.map((todo, i) => 
          i === index ? {...todo, completed: !todo.completed} : todo
        ));
      };
    }

4. 快速创建对象副本

五、深拷贝适用场景

1. 需要完全独立的对象副本

2. 修改嵌套属性且不希望影响原对象

3. 需要保留特殊对象类型

  • 这时候可以使用自定义实现和使用第三方库

4. 处理可能被多方修改的对象

 
posted @ 2025-06-24 22:18  雪旭  阅读(230)  评论(0)    收藏  举报