JavaScript篇:JS深浅拷贝的实现及底层原理

JavaScript篇:JS深浅拷贝的实现及底层原理
最新回答
四月的杨柳

2021-06-17 15:18:06

JavaScript深浅拷贝的实现及底层原理

一、深浅拷贝基础概念

  1. 基本定义

    浅拷贝(Shallow Copy):创建一个新对象,复制原始对象的所有属性值到新对象。如果属性值是引用类型(如对象、数组),则复制的是引用地址,新旧对象共享这些引用类型的值。

    深拷贝(Deep Copy):创建一个新对象,并递归地复制原始对象的所有属性值到新对象。对于引用类型的属性值,会创建新的引用类型对象,新旧对象完全独立,互不影响。

  2. 为什么需要拷贝

    避免副作用:防止对原始对象的修改影响到其他部分的代码。

    数据隔离:在不同模块或组件间传递数据时保持数据独立性。

    状态管理:在状态管理库(如Redux)中需要不可变数据。

    缓存机制:创建数据的快照用于比较或恢复。

  3. 拷贝的适用场景

    配置对象传递:浅拷贝,配置通常是扁平结构,不需要深拷贝。

    状态管理:深拷贝,需要完全独立的状态树。

    表单数据处理:深拷贝,防止表单修改影响原始数据。

    缓存机制:深拷贝,需要完全独立的数据快照。

    组件属性传递:浅拷贝,通常传递的是简单数据或组件引用。

二、浅拷贝的实现方式

  1. Object.assign()

    方法:用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。

    示例:const original = { a: 1, b: { c: 2 } };const copy = Object.assign({}, original);console.log(copy); // { a: 1, b: { c: 2 } }copy.a = 3; // 修改基本类型属性console.log(original.a); // 1 (不受影响)copy.b.c = 4; // 修改引用类型属性console.log(original.b.c); // 4 (受影响,浅拷贝)

    特点

    只能拷贝可枚举的自有属性。

    对于引用类型的属性,拷贝的是引用地址。

    可以接受多个源对象。

  2. 展开运算符(...)

    方法:ES6引入的展开运算符提供了一种更简洁的浅拷贝方式。

    示例:const original = { a: 1, b: { c: 2 } };const copy = { ...original };console.log(copy); // { a: 1, b: { c: 2 } }copy.a = 3; // 修改基本类型属性console.log(original.a); // 1 (不受影响)copy.b.c = 4; // 修改引用类型属性console.log(original.b.c); // 4 (受影响,浅拷贝)

    特点

    语法简洁直观。

    只能进行一层浅拷贝。

    可以方便地合并多个对象。

  3. Array.slice()和Array.concat()

    方法:对于数组,可以使用slice()或concat()方法进行浅拷贝。

    示例:const originalArray = [1, 2, { a: 3 }];const copyArray1 = originalArray.slice();const copyArray2 = originalArray.concat();console.log(copyArray1); // [1, 2, { a: 3 }]console.log(copyArray2); // [1, 2, { a: 3 }]copyArray1[2].a = 4; // 修改引用类型元素console.log(originalArray[2].a); // 4 (受影响,浅拷贝)

    特点

    只能拷贝数组的第一层。

    对于数组中的对象元素,拷贝的是引用地址。

    不会影响原数组的基本类型元素。

三、浅拷贝的局限性

浅拷贝的主要问题是无法处理嵌套的引用类型:

const original = { a: 1, b: { c: 2, d: [3, 4] }};const copy = { ...original };copy.b.c = 5; // 修改嵌套对象copy.b.d.push(5); // 修改嵌套数组console.log(original.b.c); // 5 (受影响)console.log(original.b.d); // [3, 4, 5] (受影响)

四、深拷贝的实现方式

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

    方法:通过将对象序列化为JSON字符串,再反序列化为新对象。

    示例:const original = { a: 1, b: { c: 2, d: [3, 4] }};const copy = JSON.parse(JSON.stringify(original));copy.b.c = 5; // 修改嵌套对象copy.b.d.push(5); // 修改嵌套数组console.log(original.b.c); // 2 (不受影响)console.log(original.b.d); // [3, 4] (不受影响)

    优点

    实现简单。

    适用于大多数场景。

    局限性

    无法处理函数、RegExp、Date等特殊对象。

    会丢失undefined和symbol类型的属性。

    无法处理循环引用(会抛出错误)。

    性能较差(需要序列化和反序列化)。

  2. 递归实现深拷贝

    方法:手动实现递归深拷贝可以解决JSON方法的一些局限性。

    示例:function deepClone(obj, hash = new WeakMap()) { if (obj === null || typeof obj !== 'object') { return obj; } if (obj instanceof Date) { return new Date(obj); } if (obj instanceof RegExp) { return new RegExp(obj); } if (typeof obj === 'function') { return obj; } if (hash.has(obj)) { return hash.get(obj); } const cloneObj = Array.isArray(obj) ? [] : {}; hash.set(obj, cloneObj); for (const key in obj) { if (obj.hasOwnProperty(key)) { cloneObj[key] = deepClone(obj[key], hash); } } return cloneObj;}const original = { a: 1, b: { c: 2, d: [3, 4] }, e: new Date(), f: /regex/g, g: function() { console.log('hello'); }};const copy = deepClone(original);copy.b.c = 5;copy.b.d.push(5);copy.e.setFullYear(2020);console.log(original.b.c); // 2 (不受影响)console.log(original.b.d); // [3, 4] (不受影响)console.log(original.e.getFullYear()); // 原始年份 (不受影响)

    特点

    可以处理日期对象、正则表达式、函数等特殊对象。

    可以处理循环引用。

    性能较好。

五、底层原理深入分析

  1. JavaScript内存模型

    JavaScript中的对象和数组都是引用类型,存储在堆内存中。

    变量存储的是对象的引用地址,而不是对象本身。

  2. 浅拷贝的底层实现

    浅拷贝只是复制了对象的引用地址,新旧对象共享同一个引用类型的值。

    修改引用类型的属性会影响原始对象。

  3. 深拷贝的底层实现

    深拷贝会递归地复制对象的所有属性值,包括嵌套的引用类型。

    新旧对象完全独立,互不影响。

  4. 性能差异分析

    浅拷贝的性能较好,因为它只是复制了引用地址。

    深拷贝的性能较差,因为它