1.什么是位置参数?
JavaScript:为什么命名参数比位置参数更好
你一定很熟悉位置参数,即使你第一次听到这个名字。
function greet(firstName, lastName) { console.log(`Hello ${firstName} ${lastName}`); } // 预期用法 greet('Michael', 'Scott'); const fName = 'Harry'; const lName = 'Potter'; greet(fName, lName); // 错误用法 const firstName = 'Erlich'; const lastName = 'Bachman'; greet(lastName, firstName);
greet函数接受两个参数:firstName和lastName。调用者必须确保firstName是第一个参数,lastName是第二个参数。这里重要的一点是,参数的名称没有任何意义,唯一重要的是参数传递的顺序。
这种熟悉的方法称为位置参数。通常,在你传递一个或两个参数的情况下,这很好,因为它很难弄乱参数的顺序。但是如果你必须调用一个需要6个参数的函数,那就很难记住传递参数的顺序。你不希望传递密码来代替用户名参数。
2. 位置参数问题
位置参数很简单,但是你将面临一些挑战。
(1) 不能跳过中间参数 /
假设你已经更改了greet函数,使其现在需要3个参数:firstName、middleName和lastName。由于许多人没有中间名,因此你希望将MiddleName设为可选参数,仅使用firstName和lastName调用greet函数的唯一方法是此方法。
greet('Aditya', null, 'Agarwal'); // Correct greet('Aditya', 'Agarwal'); // Incorrect
你不能只提供firstName和lastName。当可选参数的数量增加到5个时,这个问题变得更加明显。现在,你必须提供5个null才能在这些参数之后提供参数。
(2) 将类型添加到位置参数不那么干净
如今,为你的实用程序添加类型变得非常普遍。使用位置参数,你别无选择,只能将类型与函数定义一起内联。这可能会使代码有点模糊,如果我们可以在一个块中声明所有参数的类型定义,那就更好了。
(3) 引起细微的错误
位置参数包装了很多隐性行为,这可能是造成微妙bug的原因。我们来看一个常见的JS技巧问题
const numbers = ['1', '4', '8', '10'];
console.log(numbers.map(parseInt));
// 你可能会认为结果将是:
[1, 4, 8, 10]
// 这是实际的输出:
[ 1, NaN, NaN, 3 ]
惊讶吗?这种奇怪的输出的原因隐藏在位置参数的隐性背后。你会看到map和parseInt函数在显而易见的情况下隐藏了它们的一些秘密。
让我们再次查看代码 number.map(parseInt)。
这里到底发生了什么?
我们在numbers数组上运行map函数。
map获取数组的第一项并将其传递给parseInt。
现在,对于数组中的第一项(即1),它将执行 parseInt(1)。对...?错误!!!
实际上,map将三个参数传递给其回调函数。第一个是数组中的当前项目,第二个是项目的索引,第三个是整个数组。这本身没有问题,但真正的问题在于后一部分。
numbers.map(parseInt) 与 numbers.map((item) => parseInt(item)) 不同。你可以假设,由于回调函数仅接受item参数并将其传递给parseInt,因此我们可以跳过附加步骤。但是两者是不同的:在前者中,我们将所有数据从map传递到parseInt,而在后者中,我们仅传递项。
你可能不知道,但是parseInt的第二个参数称为基数。默认情况下,基数的值为10(以10为底,因为人类遵循十进制进行计数)。该代码出了问题,就是我们将当前项目的索引作为基数值传递给parseInt。这些是发生的实际函数调用:
parseInt('1', 0, [...]);
parseInt('4', 1, [...]);
parseInt('8', 2, [...]);
parseInt('10', 3, [...]);
现在我们知道了问题,我们如何才能做得更好?
3. 位置参数的替代
如果一个函数可以通过名字就知道它期望的参数是什么呢?这样即使你误传了额外的数据给它,它也只会使用它需要的东西。
让我们对parseInt进行包装。下面是一个简单的实现。
// 实现 function myCustomParseInt(objArgs) { return parseInt(objArgs.item, objArgs.radix); } // 使用 const num = myCustomParseInt({ item: '100', radix: 10 });
myCustomParseInt仅接受一个参数,它是一个对象。这个对象可以有两个键:item 和 radix。让我们使用我们的自定义函数与map。必须有一个中间步骤,将回调收到的args发送到myCustomParseInt。
const numbers = ['1', '4', '8', '10'];
const result = numbers.map((item, index) => myCustomParseInt({ item, index }));
console.log(result); // [ 1, 4, 8, 10 ]
请注意,即使我们将索引传递给myCustomParseInt也不会造成任何问题。那是因为myCustomParseInt只会忽略它。将对象传递给函数的这种模式称为命名参数,它比位置参数更明确。