手把手教你:JS 中实现深浅拷贝的方法

396 阅读5分钟

手把手教你:JS 中实现深浅拷贝的方法

在JavaScript的世界里,深拷贝和浅拷贝是两个重要的概念,尤其在处理对象和数组时尤为重要。了解它们的区别可以帮助你避免在操作数据时出现意外的副作用。今天,让我们一起用放大镜来仔细观察这两种神秘技术——深拷贝浅拷贝,探索它们的奥秘、应用场景及实现方法。

(一)浅拷贝:只复制一层的便民小工具

浅拷贝意味着创建一个新对象或数组,并复制原始对象或数组的所有可枚举的、非函数的属性。如果属性是引用类型(如对象或数组),那么只复制其引用,而不是复制引用所指向的实际对象。这意味着原始对象和拷贝后的对象共享同一份数据。

示例代码:

function shallowCopy(obj) {
    if (typeof obj !== 'object' || obj == null) {
        return obj;
    }

    var newObj = Array.isArray(obj) ? [] : {};
    for (var key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = obj[key];
        }
    }
    return newObj;
}

// 使用示例
const original = { a: 1, b: { c: 2 } };
const copied = shallowCopy(original);
copied.b.c = 20;
console.log(original.b.c); // 输出:20

从代码中可以看到,当我们修改copied.b.c的值时,原始original对象中b.c的值也随之改变,显示了浅拷贝的直接效果。

(1)如何进行浅拷贝

  1. 使用 Object.assign() Object.assign() 方法可以用于将源对象的所有可枚举的、非函数属性复制到目标对象。

    const obj1 = { a: 1, b: { c: 2 } };
    const obj2 = Object.assign({}, obj1);
    
    
  2. 使用扩展运算符 (...) 扩展运算符可以用于浅拷贝对象或数组。

    const arr1 = [1, 2, [3]];
    const arr2 = [...arr1];
    
    
  3. 使用循环 对于数组,可以通过循环遍历并逐个元素复制来实现浅拷贝。

    const arr1 = [1, 2, 3];
    const arr2 = [];
    for (let i = 0; i < arr1.length; i++) {
      arr2[i] = arr1[i];
    }
    
    

(2)浅拷贝的局限性

浅拷贝的缺点是它不会复制嵌套的对象或数组,而是复制它们的引用。这意味着对原始对象内部的修改会影响到拷贝后的对象。

(二)深拷贝:层层递进的全方位复制

相对于浅拷贝,深拷贝则像是一个细心的艺术家,不留一丝痕迹地复制每一层数据。深拷贝意味着创建一个新对象或数组,并递归地复制原始对象或数组的所有属性,包括嵌套的对象或数组。这样,原始对象和拷贝后的对象不再共享任何数据,原始数据和新数据之间完全独立。

(1)如何进行深拷贝

  1. 使用 JSON 方法 你可以通过将对象转换为 JSON 字符串,然后再将其解析回对象,以此来实现深拷贝。

    const obj1 = { a: 1, b: { c: 2 } };
    const obj2 = JSON.parse(JSON.stringify(obj1));
    
    

    注意:这种方法不适用于含有循环引用的对象、函数、Date 对象、正则表达式、MapSet 或其他不可序列化的类型。

  2. 使用库函数 有许多第三方库(如 lodash)提供了深拷贝的功能,这些库通常能更好地处理复杂的数据结构。

    const _ = require('lodash');
    const obj1 = { a: 1, b: { c: 2 } };
    const obj2 = _.cloneDeep(obj1);
    
    
  3. 手动实现 你也可以手动编写递归函数来实现深拷贝。

    function deepCopy(obj, hash = new WeakMap()) {
        if (obj === null || typeof obj !== 'object') {
            return obj;
        }
    
        if (hash.has(obj)) {
            return hash.get(obj);
        }
    
        var cloneObj = Array.isArray(obj) ? [] : {};
        hash.set(obj, cloneObj);
    
        for (var key in obj) {
            if (obj.hasOwnProperty(key)) {
                cloneObj[key] = deepCopy(obj[key], hash);
            }
        }
        return cloneObj;
    }
    
    // 使用示例
    const original = { a: 1, b: { c: 2 } };
    const copied = deepCopy(original);
    copied.b.c = 20;
    console.log(original.b.c); // 输出:2
    
    

在这段代码里,deepCopy函数递归地复制每一层元素,并用WeakMap来处理循环引用的问题。这样修改copied.b.c时,原始的original对象保持不变,这就是深拷贝的魔法。

(2)深拷贝的优点

深拷贝的主要优点是它完全独立于原始对象,修改拷贝后的对象不会影响到原始对象。

(3)核心技术解析

  • 递归算法:深拷贝中使用递归来遍历所有层级,关键在于正确处理每一种数据类型和防止循环引用。
  • 浅拷贝解决效率问题:在需要优化性能和资源消耗时,浅拷贝提供了一个高效的解决方案。

(四)总结

  • 浅拷贝:只复制顶层属性的值,对于引用类型的属性仅复制引用。
  • 深拷贝:递归复制所有层级的属性,确保完全独立的副本。

(五)结论

理解深浅拷贝在数据处理和性能优化中扮演的角色是每一个JavaScript开发者的必修课。希望本文的深入剖析能帮助你在未来的编程旅程中更好地选择和使用这两种技术。

选择使用浅拷贝还是深拷贝取决于你的具体需求和数据结构的复杂度。在大多数情况下,浅拷贝足以满足需求,而在处理复杂数据结构或需要确保数据完全独立的情况下,则应考虑使用深拷贝。

深入JavaScript的型态复制旅程总是充满惊喜和挑战。如同魔法师般挥洒着代码的魔杖,我们通过不断实践和学习,无疑能够将这项技术运用得游刃有余。不要忘记,掌握好深浅拷贝,代码的世界也会变得更加广阔和精彩!