翻译:道奇
作者:Dmitri Pavlutin
原文:dmitripavlutin.com/javascript-…
函数是执行某个特定任务的一段具有紧密联系的代码块,它通过参数与外部交互。 要写简洁高效的Javascript代码,就需要精通函数的参数。
本文会通过几个例子来介绍Javascript必须有效的处理函数参数的所有特性。
函数参数
Javascript函数可以有任意数量的参数。 下面分别定义了0,1,2个参数的函数:
// 0 个参数
function zero() {
return 0;
}
// 1 个参数
function identity(param) {
return param;
}
// 2 个参数
function sum(param1, param2) {
return param1 + param2;
}
zero(); // => 0
identity(1); // => 1
sum(1, 2); // => 3
以上三个函数在调用时传入了和函数参数同样数量的参数。但是实际上调用函数时可以传入少于函数参数数量的参数,这种情况下Javascript是不会报错的,调用时没有对应参数传入的函数参数会初始化为undefined。
例如,调用sum函数(有两个参数)时只传入一个参数:
function sum(param1, param2) {
console.log(param1); // 1
console.log(param2); // undefined
return param1 + param2;
}
sum(1); // => NaN
上面代码中,调用sum函数只传入了一个参数:sum(1)。那么param1会被赋值为1,第二个参数param2被初始化为undefined。param1+param2相当于1+undefined,结果就是NaN了。
如果需要的话,可验证一下参数传入的值,如果参数的值是undefined就提供一个默认值。下面是给param2设置一个默认值0:
function sum(param1, param2) {
if (param2 === undefined) {
param2 = 0;
}
return param1 + param2;
}
sum(1); // => 1
但是其实有更好办法设置默认值。
默认参数
ES2015中默认参数声明时可以设置默认值。这种方法就比上面介绍的方法更好、更简洁。
使用ES2015的默认参数特性来设置param2的默认值为0:
unction sum(param1, param2 = 0) {
console.log(param2); // => 0
return param1 + param2;
}
sum(1); // => 1
sum(1, undefined); // => 1
上面代码的函数声明中param2=0,在param2没有对应参数传入时,会将param2赋值0。所以函数传入1个参数时:sum(1),第二个参数param2的值是0。如果手动传入第二个参数的值是undefined,param2也会被赋值为0。
参数解构
我尤其喜欢函数参数解构的能力。可以对参数的对象或数组进行内联解构。 这个特性在提取参数对象的一部分属性时很有用:
function greet({ name }) {
return `Hello, ${name}!`;
}
const person = { name: 'John Smith' };
greet(person); // => 'Hello, John Smith!'
参数{name}会进行对象解构,如果通过解构设置参数的默认值就非常轻松了:
function greetWithDefault({ name = 'Unknown' } = {}) {
return `Hello, ${name}!`;
}
greetWithDefault(); // => 'Hello, Unknown!'
{ name = 'Unknown' } = {}这个赋值相当于设置参数的默认值为空对象。还可以结合不同类型的解构一起使用,例如,在同一个参数上同时使用对象和数组解构:
function greeFirstPerson([{ name }]) {
return `Hello, ${name}!`;
}
const persons = [{ name: 'John Smith' }, { name: 'Jane Doe'}];
greeFirstPerson(persons); // => 'Hello, John Smith!'
参数[{name}]的解构会更复杂,上面代码中抽取了数组的第一项,然后读取第一项的name属性。
arguments对象
Javascript函数另外一个好用的特性是能够使用可变数量的参数调用相同的函数。如果要这样做的话,可以使用一个特殊对象的参数arguments,arguments是一个类似数组的对象,它包含了函数所有的参数。
例如,将函数的参数相加:
function sumArgs() {
console.log(arguments); // { 0: 5, 1: 6, length: 2 }
let sum = 0;
for (let i = 0; i < arguments.length; i++) {
sum += arguments[i];
}
return sum;
}
sumArgs(5, 6); // => 11
arguments包含了调用函数时的所有参数。arguments是个类似数组的对象,因此数组的所有花哨方法都没法使用。另外一个问题是每个函数的作用域内都定义了自己的arguments对象。因此可能需要另外的变量来访问外部函数作用域内的arguments:
function outerFunction() {
const outerArguments = arguments;
return function innerFunction() {
// outFunction arguments
outerArguments[0];
};
}
箭头函数
有个特殊的情况:arguments不能在箭头函数中使用。
const sumArgs = () => {
console.log(arguments);
return 0;
};
// throws: "Uncaught ReferenceError: arguments is not defined"
sumArgs();
在箭头函数中arguments没有被定义。但是这不是什么大问题,可以使用rest参数访问箭头函数中的所有参数,下一段就来看一下怎样实现的。
Rest参数
ES2015中rest参数让我们可以将函数所有的参数放进一个对象中。下面是一个用Rest参数将参数求和的例子:
function sumArgs(...numbers) {
console.log(numbers); // [5, 6]
return numbers.reduce((sum, number) => sum + number);
}
sumArgs(5, 6); // => 11
...number是一个rest参数,它把参数放进一个对象[5,6],因为numbers是个数组,可以很轻松的在numbers上(对比arguments是个类数组的对象)使用数组所有花哨的方法。
arguments在箭头函数内部是不能使用的,而rest参数是可以使用的:
const sumArgs = (...numbers) => {
console.log(numbers); // [5, 6]
return numbers.reduce((sum, number) => sum + number);
}
sumArgs(5, 6); // => 11
如果不是所有的参数需要被放进rest参数,还可以结合普通的参数和rest参数一起使用。
function multiplyAndSumArgs(multiplier, ...numbers) {
console.log(multiplier); // 2
console.log(numbers); // [5, 6]
const sumArgs = numbers.reduce((sum, number) => sum + number);
return multiplier * sumArgs;
}
multiplyAndSumArgs(2, 5, 6); // => 22
multiplier是个普通的参数,它获取了第一个参数的值,然后是rest参数...numbers收到了剩下的所有参数的值。注意,每个函数最多可以有一个rest参数,并且rest参数必须位于函数参数列表的最后。
总结
除了基本用法之外,JavaScript在处理函数参数时还提供了许多有用的特性。
当缺少参数时,可以很容易地给参数设置默认值。
所有Javascript解构函数可以应用于参数,你甚至可以将解构和默认参数结合起来使用。
arguments是一个特殊的类数组的对象,它包含了函数调用时使用的所有参数。
作为arguments的更好的替换方法,可以使用rest参数特性,它也包含了所有的参数列表,但它是以数组存储的。还可以将普通参数和rest参数一起使用,然而后者必须在参数列表的最后。