同一个坑Python掉了一次JS还掉一次,很有生活了

值传递vs引用传递

以下为ChatGPT解答

在 JavaScript 中,函数参数传递遵循“按值传递”(pass by value),但对象和数组等复杂数据类型是按引用访问的(pass by reference-like behavior)。


1. 按值传递(原始类型)

以下类型是按值传递,即传递的是拷贝,函数内部修改参数不会影响原始变量:

  • ​number​(数值)
  • ​string​(字符串)
  • ​boolean​(布尔值)
  • ​undefined​
  • ​null​
  • ​bigint​
  • ​symbol​

示例

function changeValue(x) {
    x = 99; // 修改的是参数 x 的副本
}

let num = 42;
changeValue(num);
console.log(num); // 42 ✅ 原值未变

解释:
​x​ 在 changeValue()​ 里是 num​ 的拷贝,函数内部的修改不会影响原始变量。


2. 按引用访问(对象类型)

以下类型是按“引用”访问(实际上仍是按值传递),即传递的是对象的引用地址,函数内部修改对象的内容会影响原始对象:

  • ​Object​
  • ​Array​
  • ​Function​
  • ​Date​
  • ​RegExp​
  • ​Map​
  • ​Set​
  • 其他引用类型(如 WeakMap​、WeakSet​)

示例

function modifyObject(obj) {
    obj.name = "Bob"; // 修改的是 obj 的引用指向的对象
}

let person = { name: "Alice" };
modifyObject(person);
console.log(person.name); // "Bob" ✅ 原对象被修改

解释:

  • ​obj​ 传递的是 person​ 的引用,指向同一个内存地址。
  • 修改 obj.name​,person.name​ 也会变,因为它们指向同一个对象。

3. 重新赋值 vs. 修改内容

虽然对象是按引用访问的,但如果在函数内部重新赋值,不会影响外部对象:

function reassignObject(obj) {
    obj = { name: "Charlie" }; // 重新指向新对象
}

let person = { name: "Alice" };
reassignObject(person);
console.log(person.name); // "Alice" ❌ 原对象未变

解释:

  • ​obj = { name: “Charlie” }​ 只是改变了 obj​ 的本地引用,person​ 仍然指向原对象,不受影响。

如果想要让函数重新赋值影响外部对象,必须返回新对象:

function reassignObject(obj) {
    return { name: "Charlie" }; // 返回新对象
}

let person = { name: "Alice" };
person = reassignObject(person);
console.log(person.name); // "Charlie" ✅ 重新赋值生效

4. 复杂情况:数组 & 深拷贝

数组的行为类似对象,传递的是引用:

function modifyArray(arr) {
    arr[0] = 99; // 修改的是数组内部内容
}

let numbers = [1, 2, 3];
modifyArray(numbers);
console.log(numbers); // [99, 2, 3] ✅ 原数组被修改

但如果重新赋值数组,不影响原数组:

function reassignArray(arr) {
    arr = [99, 100, 101]; // 重新指向新数组
}

let numbers = [1, 2, 3];
reassignArray(numbers);
console.log(numbers); // [1, 2, 3] ❌ 原数组未变

如何避免修改原对象/数组

  • 浅拷贝(slice()​、spread operator​)
function modifyCopy(arr) {
    let copy = [...arr]; // 创建新数组
    copy[0] = 99;
}

let numbers = [1, 2, 3];
modifyCopy(numbers);
console.log(numbers); // [1, 2, 3] ✅ 原数组未变
  • 深拷贝(JSON.parse(JSON.stringify(obj))​)
function deepCopy(obj) {
    return JSON.parse(JSON.stringify(obj));
}

let original = { a: { b: 1 } };
let copy = deepCopy(original);
copy.a.b = 99;

console.log(original.a.b); // 1 ✅ 原对象未变

总结

数据类型传递方式影响原值?例子
​number​,string​,boolean​,null​,undefined​,symbol​,bigint​按值传递❌ 不影响​function(x) { x = 99; }​
​Object​,Array​,Function​,Map​,Set​等按引用访问✅ 影响​function(obj) { obj.name = “Bob”; }​
重新赋值对象/数组按值传递(新引用)❌ 不影响​function(obj) { obj = { new: “value” }; }​
​JSON.parse(JSON.stringify(obj))​深拷贝❌ 不影响​function deepCopy(obj) { return JSON.parse(JSON.stringify(obj)); }​

结论

  1. 原始类型(基本类型)总是按值传递,不会影响原值。
  2. 对象和数组是按“引用”访问,修改属性或元素会影响原值。
  3. 重新赋值对象或数组不会影响外部变量,因为函数参数始终是按值传递的(只是这个“值”是引用地址)。
  4. 如果要避免修改原值,可以使用 slice()​、…​ 或 JSON.parse(JSON.stringify(obj))​ 进行拷贝。

这就是 JavaScript 传参的引用与值的区别! 🚀


狗尾续貂:eg

a = [[6],[7,8]];function s(b){b[0]=6;}s(a[1]);

最终a的值为[[6],[6,8]]

引用类型的值比较

传引用的类型值也不可直接比较!

eg.

let arr1 = [1, 2, 3];
let arr2 = [1, 2, 3];

console.log(arr1 === arr2); // false ❌  (不同的引用)
console.log(arr1 == arr2);  // false ❌  (不同的引用)

正确操作(ChatGPT)

方法适用场景代码简洁性性能局限性
===仅适用于同一引用的数组✅ 简单✅ 快速❌ 不能比较不同引用但内容相同的数组
JSON.stringify()简单一维数组✅ 简单❌ 性能较差(字符串转换开销)❌ 不能比较复杂对象或 undefinedNaN
递归 every()适用于多维数组❌ 需要额外代码✅ 比 JSON.stringify❌ 需要额外实现
_.isEqual() (Lodash)适用于复杂嵌套结构✅ 简单✅ 高效❌ 需要引入 lodash

最佳选择

  • 简单数组:使用 JSON.stringify()
  • 多维数组或对象数组:使用递归 every()
  • 项目已有 lodash:直接用 _.isEqual()