ECMAScript 中的函数使用 function
关键字来声明,后跟一组参数以及函数体。函数的基本语法如下所示:
1 | function functionName (arg0, arg1, ... , argN) { |
以下是一个函数示例:
1 | function sayHi (name, message) { |
这个函数可以通过其函数名来调用,后面还要加上一对圆括号和参数(圆括号中的参数如果有多个,可以用逗号隔开)。调用 sayHi()
函数的代码如下所示:
1 | sayHi("Nicholas", "how are you today?"); |
ES 中的函数在定义时不必指定是否返回值。实际上,任何函数在任何时候都可以通过 return
语句后跟要返回的值来实现返回值。
1 | function sum (num1, num2) { |
这个 sum()
函数的作用是把两个值加起来返回一个结果。除了 return
语句之外,没有任何声明表示该函数会返回一个值。调用这个函数的代码如下:
1 | var result = sum(5, 10); |
这个函数会在执行完 return
语句之后停止并立即退出。因此,位于 return
语句之后的任何代码都永远不会执行。例如:
1 | function sum (num1, num2) { |
这个例子中,由于调用 alert()
函数的语句位于 return
语句之后,因此永远不会显示警告框。
当然,一个函数中也可以包含多个 return
语句,如下面这个例子中所示:
1 | function diff (num1, num2) { |
这个例子中定义的 diff()
函数用于计算两个数值的差。如果第一个数比第二个小,则用第二个数减第一个数;否则,用第一个数减第二个数。代码中的两个分支都具有自己的 return
语句,分别用于执行正确的计算。
另外,return
语句也可以不带有任何返回值。在这种情况下,函数在停止执行后将返回 undefined
值。这种用法一般用在需要提前停止函数执行而又不需要返回值的情况下。比如在下面这个例子中,就不会显示警告框:
1 | function sayHi (name, message) { |
推荐的做法是要么让函数始终都返回一个值,要么永远都不要返回值。否则,如果函数有时候返回值,有时候不返回值,会给调试代码带来不便。
严格模式下对函数有一些限制:
- 不能把函数命名为
eval
或arguments
; - 不能把参数命名为
eval
或arguments
; - 不能出现两个命名参数同名的情况。
如果发生以上情况,就会导致语法错误,代码无法执行。
1.理解参数
ES 函数的参数与大多数其他语言中函数的参数有所不同。ES 函数不介意传递进来多少个参数,也不在乎传进来的参数是什么数据类型。也就是说,即使定义的函数只接收两个参数,在调用这个函数时也未必一定要传递两个参数。可以传递一个、三个甚至不传递参数,而解析器永远不会有什么怨言。之所以会这样,原因是 ES 中的参数在内部是用一个数组来表示的。函数接收到的始终都是这个数组,而不关心数组中包含哪些参数(如果有参数的话)。如果这个数组中不包含任何元素,无所谓;如果包含多个元素,也没有问题。实际上,在函数体内可以通过 arguments
对象来访问这个参数数组,从而获取传递给函数的每一个参数。
起始,arguments
对象只是与数组类似(它并不是 Array 的实例),因为可以使用方括号语法访问它的每一个元素,使用 length
属性来确定传递进来多少个参数。在前面的例子中,sayHi()
函数的第一个参数名字叫 name
,而该参数的值也可以通过访问 arguments[0]
来获取。因此,那个函数也可以像下面这样重写,即不显示地使用命名参数:
1 | function sayHi () { |
这个重写后的函数中不包含命名的参数。虽然没有使用 name
和 message
标识符,但函数的功能依旧。这个事实说明了 ES 函数的一个重要特点:命名的参数只提供便利,但不是必需的。另外,在命名参数方面,其他语言可能需要事先创建一个函数签名,而将来的调用必需与该签名一致。但在 ES 中,没有这些条条框框,解析器不会验证命名参数。
通过访问 arguments
对象的 length
属性可以获知有多少个参数传递给了函数。下面这个函数会在每次被调用时,输出传入其中的参数个数:
1 | function howManyArgs() { |
执行以上代码会依次出现 3 个警告框,分别显示 2
、0
和 1
。利用这一点可以让函数能够接收仁义和参数并分别实现适当的功能。例如:
1 | function doAdd() { |
函数 doAdd()
会在只有一个参数的情况下给该参数加上 10
;如果是两个参数,则将那个参数简单相加并返回结果。因此,doAdd(10)
会返回 20
,而 doAdd(30, 20)
则返回 50
。
另一个与参数相关的重要方面,就是 arguments
对象可以与命名参数一起使用,如下面的例子所示:
1 | function doAdd(num1, num2) { |
关于 arguments
的行为,还有一点比较有意思,那就是它的值永远与对应命名参数的值保持同步。例如:
1 | function doAdd(num1, num2) { |
每次执行这个 doAdd()
函数都会重写第二个参数,将第二个参数的值修改为 10
。因为 arguments
对象中的值会自动反映到对应的命名参数,所以修改 arguments[1]
,也就修改了 num2
,结果它们的值都会变成 10
。不过,这并不是说读取这两个值会访问相同的内存空间;它们的内存空间是独立的,但它们的值会同步。另外,如果只传入了一个参数,那么为 arguments[1]
设置的值不会反应到命名参数中。这是因为 arguments
对象的长度是由传入的参数个数决定的,不是由定义函数时的命名参数的个数决定的。
关于参数还要记住最后一点:没有传递值的命名参数将自动被赋予 undefined
值。这就跟定义了变量但又没有初始化一样。
严格模式对如何使用 arguments
对象做出了一些限制。首先,像前面例子中那样的赋值会变得无效。也就是说,即使把 arguments[1]
设置为 10
,num2
的值仍然还是 undefined
。其次,重写 arguments
的值会导致语法错误(代码将不会执行)。
ES 中的所有参数传递的都是值,不可能通过引用传递参数。
2.没有重载
ES 函数不能像传统意义上那样实现重载。而在其他语言中,可以为一个函数编写两个定义,只要这两个定义的签名(接受的参数的类型和数量)不同即可。如前所述,ES 函数没有签名,因为其参数是由包含零或多个值的数组来表示。而没有函数签名,真正的重载是不可能做到的。
如果在 ES 中定义了两个名字相同的函数,则该名字只属于后定义的函数。请看下面的例子:
1 | function addSomeNumber(num) { |
在此,函数 addSomeNumber()
被定义了两次。第一个版本给参数加 10
,而第二个版本给参数加 200
。由于后定义的函数覆盖了先定义的函数,因此当在最后一行代码中调用这个函数时,返回的结果就是 300
。
如前所述,通过检查传入函数中参数的类型和属性并作出不同的反应,可以模仿方法的重载。