问题

JavaScript中对 function 的参数进行重新赋值的影响?

回答
在JavaScript这门语言里,函数参数的处理方式,尤其是对它们的“重新赋值”行为,其实是一个挺有意思的话题,涉及到变量作用域、值传递以及JavaScript内部的一些特性。我们得深入聊聊这个,而不是简单地列几个点。

首先,理解JavaScript函数参数传递的本质非常重要。不同于某些强类型语言直接传递“地址”或“引用”,JavaScript在传递基本类型(比如数字、字符串、布尔值、null、undefined、Symbol、BigInt)时,是进行“值传递”的。这意味着当一个基本类型的值被传递给函数时,函数内部接收到的实际上是那个值的一个副本。

而对于对象(包括数组、函数本身等),JavaScript传递的是“值”,但这个值是什么呢?它不是对象本身,而是对象的引用(或者你可以理解为指向对象的内存地址的副本)。所以,如果你通过函数参数来修改对象内部的属性,那么这种修改会影响到函数外部的那个对象,因为它们都指向同一个实际的对象。

现在我们回到“重新赋值”这个概念。假设我们有一个函数,接收一个参数,比如:

```javascript
function modifyParameter(param) {
param = 100; // 重新赋值
console.log("Inside function:", param);
}

let myVariable = 50;
modifyParameter(myVariable);
console.log("Outside function:", myVariable);
```

在这个例子里,`myVariable` 是一个数字,属于基本类型。当 `modifyParameter` 被调用时,`myVariable` 的值 `50` 被复制一份,然后传递给函数内部的 `param`。在函数内部,`param = 100;` 这条语句并不是修改 `myVariable` 本身,而是让函数内部的 `param` 这个局部变量指向了一个新的值 `100`。`myVariable` 依旧是 `50`。所以,输出会是:

```
Inside function: 100
Outside function: 50
```

这说明,对于基本类型,函数内部对参数的重新赋值,仅仅改变了函数内部这个参数变量的指向,对函数外部的原始变量没有任何影响。这就像你把一本书(`myVariable`)借给朋友,朋友在书里面写了点东西(`param = 100`),但这写东西的行为是写在他自己的一张纸上(`param` 这个局部变量),并不是直接涂改你借给他的那本书。

那如果参数是一个对象呢?我们来看看:

```javascript
function modifyObject(obj) {
obj.property = "modified"; // 修改对象的属性
obj = { property: "new object" }; // 重新赋值
console.log("Inside function (obj):", obj);
}

let myObject = { property: "original" };
modifyObject(myObject);
console.log("Outside function (myObject):", myObject);
```

在这个例子里,`myObject` 是一个对象。当 `modifyObject` 被调用时,`myObject` 的引用被复制一份,传递给函数内部的 `obj`。

首先,`obj.property = "modified";` 这行代码执行时,函数内部的 `obj` 指向的对象,其 `property` 属性被修改了。因为 `obj` 和 `myObject` 指向的是同一个实际的对象,所以函数外部的 `myObject` 的 `property` 属性也会变成 `"modified"`。

接着,`obj = { property: "new object" };` 这行代码执行了。这里,函数内部的 `obj` 这个局部变量的引用被改变了,它现在指向了一个全新的对象 `{ property: "new object" }`。但是,这个重新赋值行为只改变了函数内部 `obj` 这个变量的指向,它并不会去寻找 `obj` 最初指向的那个内存地址,然后改变那个内存地址里的内容,或者让外部的 `myObject` 也指向这个新对象。`myObject` 依旧指向那个最初被创建的对象,只是那个对象的 `property` 属性已经被修改过了。

所以,输出结果会是:

```
Inside function (obj): { property: 'new object' }
Outside function (myObject): { property: 'modified' }
```

从这个结果我们可以清晰地看到:

1. 修改对象的属性:在函数内部修改对象属性 (`obj.property = "modified"`),会影响到函数外部的原始对象,因为它们共享相同的引用。
2. 重新赋值参数:在函数内部对参数进行重新赋值 (`obj = { property: "new object" }`),仅仅改变了函数内部该参数变量的引用,不会影响到函数外部的原始变量(无论是基本类型还是对象引用)。

简单来说,JavaScript 的函数参数传递是一种“传递副本”的方式。对于基本类型,传递的是值的副本;对于对象,传递的是引用的副本。当你在函数内部对参数进行重新赋值时,你实际上是在操作这个副本(无论是值副本还是引用副本),而不是直接操作函数外部的原始变量。

这是一种保护机制,防止函数内部的随意操作影响到全局的状态,除非你是明确地通过修改对象属性这样的方式来达成。理解这一点,就能更好地把握 JavaScript 函数的运行机制了。

网友意见

user avatar

题主所说的代码形式就是为了在语言层面不支持默认参数(default parameter)功能时的变通做法。正因为它的意图是“在参数没有得到赋值时给它一个默认值”,所以常常会赋值回到原本名字的参数上(而不是新开一个别的名字的局部变量)。

在ES2015(ES6)语言层面添加了对默认参数的支持后,这种做法的必要性就大幅降低了。可参考MDN文档的介绍:

Default parameters

注意上面MDN文档中给出的使用ES6语言层面默认参数之前的解决办法:

       function multiply(a, b) {   var b = typeof b !== 'undefined' ?  b : 1;    return a*b; }  multiply(5); // 5      

这个参数检查显然比题主给的例子的“param = param || {}”更复杂。

MDN的范例版本反映了ES6语言层面的默认参数的语义——只有当传入的参数为语言内建的undefined值时才使用默认参数值,否则使用传入的参数;

而题主给的例子则是当传入参数为任意假值(undefined、null、0等等)时都采用默认参数值。这比MDN的范例的限制更宽松,因而也更有可能出现意想不到的情况。谨慎使用。

如果一个实际场景就是期待传入的参数是个对象,如题主的例子那样,那么直接用 || {}其实也够用——假值都过滤掉就正好符合需求。

=====================================

题主所说的第1和第2种情况其实没有任何区别。要干净一点的话第1种更干净。

请参考

Annotated ES5 - 10.5 Declaration Binding Instantiation

中的第4.d点和第8点。第4.d点讲解了函数的参数被放进环境的过程,而第8点则讲解了局部变量被创建和放进环境的过程——注意它强调只有当环境中一个名字还没有被声明的时候,才会创建该名字的局部变量。

(注意关于varAlredyDeclared的部分。这个规定保证了同一作用域内var变量不会被重复声明——重复的声明会等价于在开头只声明了一次。)

换句话说,这个规定也就指定了JavaScript的变量声明提升(variable declaration hoisting)的语义。在一个函数里,一个名字的变量无论被用var声明多少次,它们实际语义都等价于被合并在一起提升到函数开头的地方声明。

在声明的意义上,函数的参数就跟局部变量一样,只不过函数的参数早于所有局部变量的声明而被记录进环境里。这就是为什么题主给的第2种代码实际上跟第一种代码的意思是完全一样的——后面的var param并不会重复声明变量,也不会声明一个新的同名局部变量去遮蔽之前参数所声明的那个。

特别注意“声明”(declaration)和“定义”(definition)是不同的,前者只负责变量名在环境中的存在与否,而后者才是真正的赋值动作。举例说:

       function foo() {   var a = 1; // declaration + definition   var a;     // declaration only   var a = 3; // declaration + definition   a = 4;     // definition only }      

根据JavaScript的规定,带有初始化表达式(initialiser)的变量声明既是声明也是定义。在变量声明提升后,上述代码的实际效果跟下面的代码是一样的:

       function foo() {   var a; // declaration (hoisted)   a = 1; // definition   // var a; // (hoisted)   a = 3; // definition   a = 4; // definition }      

留意这里变量声明经过提升就只有一份有效果了,但变量定义(赋值)的每一份都还在其原本的位置上起作用。可以参考

Annotated ES5 - 12.2 Variable Statement

的规定:

A variable with an Initialiser is assigned the value of its AssignmentExpression when the VariableStatement is executed, not when the variable is created.

=====================================

另外要注意在ES2015(ES6)里的let/const声明与之前的var声明的规定不一样了,var声明的局部变量还是函数作用域的,而let/const声明的binding则是词法块作用域的。但在合并声明的意义上let/const与var有相似之处——都会检测作用域里是否已经有该名字的变量声明。

var在同一作用域里的多个同名声明会被合并,而不会创建新变量或遮蔽之前的同作用域同名变量;

而let/const则会检测当前作用域里是否已经有该名字的声明,并且拒绝重复的声明。

在JavaScript这种闭包实现了引用捕获的语言里,要验证多个声明是否创建了多个变量很简单——让多个闭包捕获变量,最后看它们捕获的值是否相同:

       function foo() {   var a = 1   var f1 = function () { return a }   var a = 2   var f2 = function () { return a }   return {     f1: f1,     f2: f2   } } var o = foo() console.log(o.f1()) // 2 console.log(o.f2()) // 2      

可以看到foo()里的f1与f2嵌套函数捕获到的a是同一个变量a,所以它们后面被调用时返回的a值一样。

如果把这个例子的变量a声明改为用let声明,

       function foo() {   let a = 1   var f1 = function () { return a }   let a = 2   let f2 = function () { return a } // Uncaught SyntaxError: Identifier 'a' has already been declared   return {     f1: f1,     f2: f2   } }      

则该函数被加载时就会报错,拒绝同一作用域里同名变量的重复声明。

参数的处理同理:

       function bar(x) {   let x; // Uncaught SyntaxError: Identifier 'x' has already been declared }      

类似的话题

  • 回答
    在JavaScript这门语言里,函数参数的处理方式,尤其是对它们的“重新赋值”行为,其实是一个挺有意思的话题,涉及到变量作用域、值传递以及JavaScript内部的一些特性。我们得深入聊聊这个,而不是简单地列几个点。首先,理解JavaScript函数参数传递的本质非常重要。不同于某些强类型语言直接.............
  • 回答
    在 JavaScript 中,定义函数的方式有两种非常常见:一种是函数声明(`function foo() {}`),另一种是函数表达式(`var foo = function() {}`)。虽然它们最终都能创建一个函数对象并赋值给变量 `foo`,但在一些关键的方面,它们有着本质的区别。理解这些区.............
  • 回答
    在 JavaScript 中,对于 `for` 循环中的 `array.length` 进行缓存,究竟有没有必要?这是一个在许多 JavaScript 开发者中都存在讨论的话题,尤其是在追求极致性能的场景下。要弄清楚这个问题,我们需要深入理解 JavaScript 引擎如何处理数组以及循环的执行过程.............
  • 回答
    想象一下,你脑子里有一个非常棒的点子,比如想做一个能给你的宠物猫拍有趣照片的小程序,或者一个能帮你计算日常开销的小工具。要把这个“脑中的东西”变成电脑能理解的代码,就像是你要给一个不太懂你的朋友解释清楚,让他一步一步地照着你的指示去做。首先,别急着往代码编辑器里敲键盘。先停下来,像个侦探一样,把你的.............
  • 回答
    您好!让我来帮您分析一下这段 JavaScript 代码在 Chrome 88 和 Firefox 85 中执行结果为 `false` 的原因。我会尽量详细地解释,并且用自然、清晰的语言来描述,避免AI写作的痕迹。请您提供具体的 JavaScript 代码。没有看到您提供的具体代码,我无法给出准确的.............
  • 回答
    .......
  • 回答
    好的,我们来聊聊构建网站时会遇到的一些核心技术。这些技术各司其职,共同协作,最终呈现在我们面前的就是一个功能丰富、交互生动的网页。 网页的骨架:HTML 与它的进化之路想象一下盖房子,你需要一个框架来支撑整个结构,确保它稳固。在网页世界里,这个框架就是 HTML (HyperText Markup .............
  • 回答
    好的,我们来好好梳理一下 JavaScript、jQuery、AJAX 和 JSON 这四个在 Web 开发中经常一起出现的概念,并尽可能讲得透彻一些,让它们之间的联系一目了然。咱们就抛开那些写出来就感觉是“机器在说话”的套话,用一种更接地气的方式来聊聊。想象一下咱们在盖房子,JavaScript .............
  • 回答
    JavaScript 凭什么不是面向对象的语言? 这句话本身就有点像个钓鱼标题,故意激化矛盾,引人入胜。但说 JavaScript “不是”面向对象的,这绝对是站不住脚的,而且会引来一堆懂行的人跟你理论。不过,我们可以这么理解:JavaScript 的“面向对象”方式,和很多传统意义上、大家更熟悉的.............
  • 回答
    JavaScript 的博大精深,很多东西其实当你用到的时候,自然而然就会去了解,无需特意去“背诵”或者“啃书”。 就像我们学习骑自行车,一开始可能需要有人扶着,但一旦掌握了平衡的诀窍,后面就不需要别人时刻提醒你的姿势了。比如说,JavaScript 中的“作用域”这个概念。初学者可能会觉得“作用.............
  • 回答
    JavaScript 的确提供了强大的机制,可以让你在函数被调用时进行干预,几乎能够实现对所有函数调用的“钩子”操作。这并不是一个简单的“列表”式的功能,而是一种通过语言特性和设计模式组合而成的能力。想象一下,你有一个庞大的 JavaScript 程序,里面充满了各种各样的函数。你希望在你执行任何一.............
  • 回答
    javascript 的 arguments 对象,可以说是语言设计中一个颇具争议的存在。从最初的设计意图来看,arguments 似乎是为了方便开发者在函数中访问所有传入的参数,无论函数声明时指定了多少个参数。这在一些其他语言中也是常见的做法,比如 C 语言的可变参数函数。但是,arguments.............
  • 回答
    JavaScript 在 V8 引擎和 Node.js 环境下的开发效率,相比于 Java 在 JVM 和 Vert.x 组合下的确有其独到之处,这主要体现在几个关键的维度上,而不是简单的功能堆砌。首先,JavaScript 的“一次编写,到处运行”的理念在 Web 开发这个根深蒂固的领域带来了巨大.............
  • 回答
    如果JavaScript具备了真正意义上的多线程能力,那它在处理并发和复杂任务时,无疑会迎来一场翻天覆地的变革。想象一下,我们不再需要依赖那些精巧的、基于事件循环的模拟多线程方案,比如Web Workers,而是能够像许多其他成熟的后端语言一样,直接创建和管理多个独立的执行线程。这会带来什么?首先,.............
  • 回答
    很多初学 JavaScript 的朋友,在使用 `if...else if...else` 语句的时候,会遇到一些似是而非的困惑,总觉得哪里不对劲,但又说不清楚。今天我们就来聊聊这个最基础,也最容易被忽视的知识点,希望能让大家彻底弄明白它。我们先抛开那些花哨的术语,直接从实际应用出发。想象一下,你要.............
  • 回答
    你提出的这个问题非常有意思,它涉及到 JavaScript 中一个非常有趣的特性——类型转换,特别是涉及到布尔值比较时。要理解为什么 `[] == true` 会是 `true`,我们需要深入了解 JavaScript 在执行相等性比较(`==`,也叫宽松相等或松弛相等)时是如何工作的。不同于严格相.............
  • 回答
    JavaScript,这门被广泛使用的编程语言,你可以把它想象成网站的“灵魂”。当你在浏览器中浏览一个网页时,你看到的美观的布局、流畅的动画、可交互的按钮,还有那些在你点击后弹出信息或者动态加载内容的精彩表现,很大一部分都离不开JavaScript的功劳。它不像HTML那样是网站的“骨架”,勾勒出页.............
  • 回答
    遇到处理100MB这种规模的XML文件导致IE未响应的情况,这在客户端JavaScript处理中确实是个棘手的难题。直接在浏览器端一股脑地加载、解析和处理如此庞大的数据,几乎是注定失败的。浏览器有限的内存和CPU资源,以及单线程的JavaScript执行模型,都会成为巨大的瓶颈。首先,我们得明白为什.............
  • 回答
    .......
  • 回答
    随着 JavaScript 的生态系统不断成熟,它的功能也日益丰富,这自然会让人产生疑问:在这个日新月异的世界里,学习 TypeScript 还有那么必要吗?毕竟,JavaScript 本身已经足够强大,能够胜任各种复杂的开发任务。然而,答案依然是肯定的,而且我认为,对于任何认真对待 JavaScr.............

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有