一、参数传递机制

聊到Ja vaScript的函数传参,有个概念是绕不开的:值传递。没错,这门语言采用的确实是值传递,但这里面的“值”,在不同类型的数据上,表现可是大不相同。简单来说,它决定了你在函数内部的操作,会不会“波及”到外部的变量。

对于基本类型,比如数字、字符串,传递进去的是值的“副本”。你在屋里怎么折腾这个副本,外面的原件都完好无损。

而对于引用类型,比如对象、数组,传递进去的则是对象“引用”的副本。这意味着,如果你通过这个引用来修改对象的属性,外面的对象会跟着变;但如果你干脆让这个参数指向一个全新的对象(也就是重新赋值),那么外面的引用依然不动如山。

二、基本类型参数(不可变)

function modifyPrimitive(num) {
  num = 100; // 修改的只是本地副本
}
let x = 5;
modifyPrimitive(x);
console.log(x); // 输出:5(原变量纹丝未动)

关键点在这里:参数num拿到的是原始值5的一个精确复制品。函数内部对这个复制品进行任何赋值操作,都如同在平行世界里进行,自然不会影响到外部那个独立的变量x

三、引用类型参数(可变)

1. 修改属性 → 影响外部

function modifyObject(obj) {
  obj.name = 'Bob'; // 通过引用找到“原对象”,并修改其属性
}
let person = { name: 'Alice' };
modifyObject(person);
console.log(person.name); // 输出:Bob(外部对象被成功修改)

这就像是你和朋友共享一份文档的链接。朋友通过链接打开文档,修改了里面的内容,你这边再打开,看到的自然就是修改后的版本。函数内的obj和外部的person,持有的是同一个对象的“地址”,通过这个地址去操作,效果是全局的。

2. 重新赋值参数 → 不影响外部

function replaceObject(obj) {
  obj = { name: 'Charlie' }; // 参数“obj”被赋予了全新对象的地址
}
let person = { name: 'Alice' };
replaceObject(person);
console.log(person.name); // 输出:Alice(原对象安然无恙)

这次的区别在于,不是在共享的文档上修改文字,而是朋友直接把给他的那个链接,指向了另一份全新的文档。你手里的链接,指向的依然是原来那份。所以,对参数进行整体的重新赋值(=操作),只是改变了参数本地副本的指向,与外部变量彻底“分手”。

3. 数组示例

function modifyArray(arr) {
  arr.push(4);      // ✅ 通过引用操作原数组,有效
  arr = [5, 6, 7];  // ❌ 重新赋值,仅改变局部参数指向,无效
}
let numbers = [1, 2, 3];
modifyArray(numbers);
console.log(numbers); // 输出:[1, 2, 3, 4]

数组作为对象,逻辑完全一致。push操作是在原有地址上的修改,而arr = ...则是给参数换了一个全新的地址,外部的numbers感知不到这个变化。

四、参数赋值 vs 属性修改

操作类型       对外部的影响      示例

修改基本类型参数    ❌ 不影响外部    function(x) { x = 10; }

修改引用类型的属性    ✅ 影响外部    function(obj) { obj.key = 1; }

重新赋值引用类型参数  ❌ 不影响外部    function(obj) { obj = {}; }

这张对比表可以帮你快速抓住核心区别。记住,区分是“通过引用修改内容”还是“给引用本身换目标”,是理解这个问题的钥匙。

五、实战分析:forEach中的参数行为

理解了上述原理,再来看Array.prototype.forEach方法里回调函数的行为,就一目了然了。回调函数接收到的item参数,本质就是数组元素(值或引用)的一个副本。

因此:

示例分析

1. 基本类型数组(重新赋值无效)

const arr = [1, 2, 3];
arr.forEach((item) => {
  item = item * 10; // 修改的是副本,不影响原数组
});
console.log(arr); // 输出:[1, 2, 3]

原因很清晰:每次迭代,item都是数组中数字123的独立副本。对副本做数学运算然后赋值,改变的只是这个临时变量,原数组的槽位没有任何写入操作。

2. 引用类型数组(修改属性有效,重新赋值无效)

const arr = [{ value: 1 }, { value: 2 }];

// 情况A:修改属性 → 有效
arr.forEach((item) => {
  item.value = item.value * 10; // 通过引用修改原对象
});
console.log(arr); // 输出:[{ value: 10 }, { value: 20 }]

// 情况B:重新赋值 → 无效
arr.forEach((item) => {
  item = { value: 100 }; // 创建新对象,副本指向新对象,原数组元素引用不变
});
console.log(arr); // 输出依然是:[{ value: 10 }, { value: 20 }]

这里揭示了两个关键点:

掌握参数传递是值传递,并分清“修改引用所指的内容”与“更换引用本身”这两种操作,就能从容应对Ja vaScript中函数操作带来的副作用问题,写出更可预测的代码。

本文转载于:https://www.jb51.net/javascript/3557340n0.htm 如有侵犯,请联系zhengruancom@outlook.com删除。
免责声明:正软商城发布此文仅为传递信息,不代表正软商城认同其观点或证实其描述。