理解参数
-
ECMAScript 函数的参数跟大多数其他语言不同。ECMAScript 函数既不关心传入的参数个数,也不关心这些参数的数据类型。定义函数时要接收两个参数,并不意味着调用时就传两个参数。你也可以传一个、三个,甚至一个也不传,解释器都不会报错。
-
之所以会这样,主要是因为 ECMAScript 函数的参数在内部表现为一个数组,函数被调用时总会接收一个数组,但函数并不关心这个数组包含什么。如果数组中什么也没有,那没有问题;如果数组的元素超出了要求,那也没有问题。事实上,在使用 function 关键字定义(非箭头)函数时,可以在函数内部访问 arguments 对象,从中获取传进来的每个参数值。
-
arguments 对象是一个类数组对象(但不是 Array 的实例),因此可以使用中括号语法访问其中的元素(第一个参数就是 arguments[0],第二个参数是 arguments[1])。而要确定传进来多少个参数,可以访问 arguments.length 属性。
-
在下面的例子中,sayHi()函数的第一个参数叫 name:
function sayHi(name, message) { console.log(`hello${name}, ${message}`); } // 可以通过arguments[0],取得相同的参数值。因此,把函数重写成不声明函数也可以 function sayHi() { console.log(`hello${arguments[0]}, ${arguments[1]}`); } -
再重写后的代码中,没有命名参数。name 和 message 参数都不见了,但函数照样可以调用。这就表明,ECMAScript 函数的参数只是为了方便才写出来的,并不是必须写出来的。与其他语言不同,再 ECMAScript 中的命名参数不会创建让之后的调用必须匹配的函数签名。这是因为根本不存在验证命名的参数的机制。
-
也可以通过 arguments 对象的 length 属性检查传入的参数个数。下面的例子展示了在每调用一个函数时,都会打印传入的参数个数:
function howManyArgs() { return arguments.length; } howManyArgs("string", 45); // 2 howManyArgs(); // 0 howMangArgs(12); // 1 -
这个例子分别打印出 2、0 和 1。既然如此,那么开发者可以想传多少个参数就传多少个参数。比如:
function doAdd() { if (arguments.length === 1) { console.log(arguments[0] + 10); } else if (arguments.length === 2) { console.log(arguments[0] + arguments[1]); } } doAdd(10); // 20 doAdd(20, 30); // 50 -
这个函数 doAdd()在只传一个参数时会加 10,在传两个参数时会将他们相加,然后返回。虽然不像真正的函数重载那么明确,但这已经足以弥补 ECMAScript 在这方面的缺失了。
-
还有一个必须理解的重要方面,那就是 arguments 对象可以跟命名参数一起使用,比如:
function doAdd(num1, num2) { if (arguments.length === 1) { console.log(num1 + 10); } else if (arguments.length === 2) { console.log(arguments[0] + num2); } } -
在这个 doAdd() 函数中,同时使用了命名参数和 arguments 对象。命名参数 num1 保存着与 arguments[0]一样的值,因此使用谁都无所谓。
-
arguments 对象的另一个有意思的地方就是,它的值始终会与对应的命名参数同步。来看下面的例子:
function doAdd(num1, num2) { argument[1] = 10; console.log(arguments[0] + arguments[1]); } -
这个 doAdd()函数把第二个参数的值重写为 10。因为 arguments 对象的值会同步到对应的命名参数,所以修改 argumenets[1]也会修改 num2 的值,因此两者的值都是 10。但这并不意味着它们都访问同一个内存地址,它们在内存中还是分开的,只不过会保持同步而已。另外还要记住一点:如果只传了一个参数,然后把 arguments[1]设置为某个值,那么这个值并不会反映到第二个命名参数。这是因为 arguments 对象的长度是根据传入的参数个数,而非定义函数时给出的命名参数个数确定的。
箭头函数中的参数
- 如果函数是使用箭头语法定义,那么传给函数的参数将不能使用 arguments 关键字访问,而只能通过定义的命名参数访问。
function foo() { console.log(arguments[0]); } foo(5); // 5 let bar = () => { console.log(arguments[0]); }; bar(5); //ReferenceError: arguments is not defined - 虽然箭头函数中没有 arguments 对象,但可以在包装函数中把它提供给箭头函数:
function foo() { let bar = () => { console.log(arguments[0]); }; bar(); } foo(5); // 5