JavaScript:为什么命名参数比位置参数更好

开发 前端
通常,在你传递一个或两个参数的情况下,这很好,因为它很难弄乱参数的顺序。但是如果你必须调用一个需要6个参数的函数,那就很难记住传递参数的顺序。

1. 什么是位置参数?

[[338903]]

你一定很熟悉位置参数,即使你第一次听到这个名字。

  1. function greet(firstName, lastName) { 
  2.   console.log(`Hello ${firstName} ${lastName}`); 
  3.  
  4. // 预期用法 
  5.  
  6. greet('Michael', 'Scott'); 
  7.  
  8. const fName = 'Harry'
  9. const lName = 'Potter'
  10. greet(fName, lName); 
  11.  
  12.  
  13. // 错误用法 
  14.  
  15. const firstName = 'Erlich'
  16. const lastName = 'Bachman'
  17. greet(lastName, firstName); 

greet函数接受两个参数:firstName和lastName。调用者必须确保firstName是第一个参数,lastName是第二个参数。这里重要的一点是,参数的名称没有任何意义,唯一重要的是参数传递的顺序。

这种熟悉的方法称为位置参数。通常,在你传递一个或两个参数的情况下,这很好,因为它很难弄乱参数的顺序。但是如果你必须调用一个需要6个参数的函数,那就很难记住传递参数的顺序。你不希望传递密码来代替用户名参数。

2. 位置参数问题

位置参数很简单,但是你将面临一些挑战。

(1) 不能跳过中间参数 /

假设你已经更改了greet函数,使其现在需要3个参数:firstName、middleName和lastName。由于许多人没有中间名,因此你希望将MiddleName设为可选参数,仅使用firstName和lastName调用greet函数的唯一方法是此方法。

  1. greet('Aditya', null, 'Agarwal'); 
  2. // Correct ✅ 
  3.  
  4. greet('Aditya', 'Agarwal'); 
  5. // Incorrect ❌ 

你不能只提供firstName和lastName。当可选参数的数量增加到5个时,这个问题变得更加明显。现在,你必须提供5个null才能在这些参数之后提供参数。

(2) 将类型添加到位置参数不那么干净

如今,为你的实用程序添加类型变得非常普遍。使用位置参数,你别无选择,只能将类型与函数定义一起内联。这可能会使代码有点模糊,如果我们可以在一个块中声明所有参数的类型定义,那就更好了。

(3) 引起细微的错误

位置参数包装了很多隐性行为,这可能是造成微妙bug的原因。我们来看一个常见的JS技巧问题

  1. const numbers = ['1', '4', '8', '10']; 
  2. console.log(numbers.map(parseInt)); 
  3.  
  4. // 你可能会认为结果将是: 
  5. [1, 4, 8, 10] 
  6.  
  7. // 这是实际的输出: 
  8. [ 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。这些是发生的实际函数调用:

  1. parseInt('1', 0, [...]); 
  2. parseInt('4', 1, [...]); 
  3. parseInt('8', 2, [...]); 
  4. parseInt('10', 3, [...]); 

现在我们知道了问题,我们如何才能做得更好?

3. 位置参数的替代

如果一个函数可以通过名字就知道它期望的参数是什么呢?这样即使你误传了额外的数据给它,它也只会使用它需要的东西。

让我们对parseInt进行包装。下面是一个简单的实现。

  1. // 实现 
  2. function myCustomParseInt(objArgs) { 
  3.   return parseInt(objArgs.item, objArgs.radix); 
  4.  
  5. // 使用 
  6. const num = myCustomParseInt({ item: '100', radix: 10 }); 

myCustomParseInt仅接受一个参数,它是一个对象。这个对象可以有两个键:item 和 radix。让我们使用我们的自定义函数与map。必须有一个中间步骤,将回调收到的args发送到myCustomParseInt。

  1. const numbers = ['1', '4', '8', '10']; 
  2.  
  3. const result = numbers.map((item, index) => myCustomParseInt({ item, index })); 
  4.  
  5. console.log(result); // [ 1, 4, 8, 10 ] 

请注意,即使我们将索引传递给myCustomParseInt也不会造成任何问题。那是因为myCustomParseInt只会忽略它。将对象传递给函数的这种模式称为命名参数,它比位置参数更明确。

要更改基数,我们必须显式传递基数键。这意味着如果要解析以2为底的字符串,则必须转到文档并查看参数(基数)的确切名称。如果我们盲目地传递任何其他键,它将无济于事。这对我们来说很棒,因为它避免了意外行为。

(1) 具有解构的命名参数

不久前,JavaScript获得了称为解构的功能,让我们在myCustomParseInt实现中使用它。

  1. // 位置参数 
  2. function myCustomParseInt(item, radix) { 
  3.   return parseInt(item, radix); 
  4.  
  5. // 命名参数旧的实现 
  6. function myCustomParseInt(objArgs) { 
  7.   return parseInt(objArgs.item, objArgs.radix); 
  8.  
  9. // 命名参数解构 
  10. function myCustomParseInt({ item, radix }) { 
  11.   return parseInt(item, radix); 

你会注意到,只需添加两个花括号,我们就可以得到命名args的好处,你可以将解构视为执行 const item = objArgs.item;。

如果使用 undefined 调用myCustomParseInt,则JS将引发错误。那是因为不允许 undefined.item。为了避免这种情况,我们可以在解构结束时添加 = {}。这样,当我们传递undefined时,它将执行 {}.item 这是有效的JS。这是最终的实现:

  1. function myCustomParseInt({ item, radix } = {}) { 
  2.   return parseInt(item, radix); 

通过命名参数模式,我们也可以跳过我们不想提供的参数,因为函数不再依赖于传递参数的顺序。

  1. // 对于位置参数,我们必须在之间添加一个null 
  2. function greetPos(firstName, middleName, lastName) {} 
  3. greetPos('Aditya', null, 'Agarwal'); 
  4.  
  5.  
  6. // 使用命名参数,你只需提供firstName和lastName。 
  7. function greetNamed({ firstName, middleName, lastName } = {}) {} 
  8. greetNamed({ firstName: 'Aditya', lastName 'Agarwal' }); 

总而言之,我要说的是命名参数是一种强大的模式,如今它已变得非常普遍,但是你不必总是使用它们。有时你甚至可以将两者结合在一起。浏览器中的fetch API的用法如下:

  1. // 以url作为位置参数的请求,以及以args做命名参数的选项。 
  2. fetch('https://google.com', { 
  3.   method: 'POST', 
  4.   headers: { 
  5.     'Content-Type': 'application/json', 
  6.   }, 
  7. }); 
  8.  
  9. // basic GET requests with just positional args 
  10. fetch('https://google.com'); 

这里的强制参数(API路径)是一个位置参数,然后通过命名参数接受可选的参数。

 

责任编辑:赵宁宁 来源: 今日头条
相关推荐

2022-11-10 15:32:29

2012-05-11 09:50:49

iOSAndroid移动应用

2014-03-26 10:09:14

指针指针使用

2020-07-17 19:31:19

PythonR编程

2020-02-14 13:53:33

Python 开发编程语言

2023-02-26 23:36:08

PHPGo函数

2023-09-27 08:22:28

Windows系统管理器

2022-09-05 10:01:19

VueReact

2024-08-13 17:29:24

2022-03-28 11:51:00

深度学习机器学习模型

2021-01-25 07:14:53

Cloud DevOps云计算

2021-08-31 23:33:50

AndroidiOS功能

2013-01-15 10:53:36

2013-01-16 14:29:22

2010-01-20 17:32:16

C++函数

2009-06-09 21:54:26

传递参数JavaScript

2017-09-20 07:57:38

AWG电缆导线

2021-05-27 07:54:21

Math.max()-Infinity参数

2021-01-13 10:51:08

PromissetTimeout(函数

2009-07-08 09:47:49

Scala 2.8Scala
点赞
收藏

51CTO技术栈公众号