在JavaScript中,变量可以分为基本数据类型和引用数据类型。基本数据类型(如Number、String、Boolean等)在传递时,会创建一个值的新副本,因此是“值传递”。而引用数据类型(如Object、Array等)在传递时,实际上传递的是对该对象或数组的引用,因此是“引用传递”。

浅拷贝与深拷贝的概念

当引用类型变量被传递时,如果我们只是简单地复制其引用,而没有复制引用所指向的对象,这就是浅拷贝。相反,如果复制了引用所指向的对象的每个属性,包括嵌套对象,这就是深拷贝。

浅拷贝

浅拷贝只会复制对象的最外层属性,对于嵌套的属性,它只会复制引用,而不是复制嵌套的对象本身。以下是一个浅拷贝的例子:

let obj = {
    name: "John",
    age: 30,
    hobbies: ["reading", "gaming"]
};

let shallowCopy = {...obj};

在这个例子中,shallowCopy只是复制了obj的最外层属性。如果修改hobbies数组,这个改变也会影响到obj

shallowCopy.hobbies.push("traveling");
console.log(obj.hobbies); // ["reading", "gaming", "traveling"]

深拷贝

深拷贝则会复制引用类型变量的所有属性,包括嵌套的引用类型属性。这意味着修改深拷贝后的对象,不会影响到原对象。以下是一个深拷贝的例子:

let obj = {
    name: "John",
    age: 30,
    hobbies: ["reading", "gaming"]
};

let deepCopy = JSON.parse(JSON.stringify(obj));

在这个例子中,deepCopyobj的深拷贝。如果修改deepCopy的属性,原对象obj不会被影响:

deepCopy.hobbies.push("traveling");
console.log(obj.hobbies); // ["reading", "gaming"]

深拷贝的实现

虽然JSON.parse(JSON.stringify(obj))可以实现深拷贝,但它有一些限制:

  1. 无法复制函数。
  2. 无法复制undefined和循环引用。

以下是一个自定义深拷贝函数的例子:

function deepClone(obj, hash = new WeakMap()) {
    if (obj === null) return null;
    if (typeof obj !== 'object') return obj;
    if (hash.has(obj)) return hash.get(obj);

    let cloneObj = new obj.constructor();
    hash.set(obj, cloneObj);

    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            cloneObj[key] = deepClone(obj[key], hash);
        }
    }
    return cloneObj;
}

在这个函数中,我们使用递归来复制对象的每个属性。WeakMap用于处理循环引用的情况。

总结

理解浅拷贝与深拷贝是JavaScript编程中非常重要的概念。通过正确地使用它们,我们可以有效地避免在对象复制过程中可能出现的副作用。在开发过程中,选择合适的拷贝方法取决于具体的应用场景和需求。