本文深入解析JavaScript中位置参数的局限性,包括参数顺序易错、中间参数无法跳过及类型定义繁琐等问题。通过parseInt在map中的常见陷阱案例,揭示位置参数导致的隐性Bug。随后介绍利用对象解构实现命名参数的模式,展示其如何提升代码可读性与安全性,并结合fetch API说明混合使用两种参数的最佳实践。
什么是位置参数
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函数在表象之下隐藏了部分机制。
重新审视代码 numbers.map(parseInt)。
具体发生了什么?
- 我们在numbers数组上执行map函数。
- map获取数组首项并传入parseInt。
- 对于数组第一项(即1),执行 parseInt(1)。对吗?错误!
实际上,map向回调函数传递三个参数:当前数组项、项索引及整个数组。这本身无误,真正问题在于后半部分。
numbers.map(parseInt) 与 numbers.map((item) => parseInt(item)) 不同。你可能认为回调仅接收item并传给parseInt,故可省略附加步骤。但二者存在差异:前者将map所有数据传给parseInt,后者仅传项。
你可能不知,parseInt的第二个参数名为基数(radix)。默认值为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亦无影响,因其会忽略该值。将对象传入函数的模式称为命名参数,较位置参数更明确。
若要更改基数,必须显式传递基数键。解析以2为底的字符串时,需查阅文档确认参数(基数)确切名称。若盲目传递其他键,则无效。这避免了意外行为,十分有益。
具有解构的命名参数
(1) 结合解构的命名参数
近期JavaScript引入解构功能,让我们可在myCustomParseInt实现中利用它。
// 位置参数
function myCustomParseInt(item, radix) {
return parseInt(item, radix);
}
// 命名参数旧的实现
function myCustomParseInt(objArgs) {
return parseInt(objArgs.item, objArgs.radix);
}
// 命名参数解构
function myCustomParseInt({ item, radix }) {
return parseInt(item, radix);
}
只需添加两个花括号,即可享受命名参数优势。可将解构视为执行 const item = objArgs.item;。
若以undefined调用myCustomParseInt,JS将报错,因不允许访问 undefined.item。为避免此情况,可在解构末尾添加 = {}。这样,传递undefined时,将执行 {}.item,此为有效JS。最终实现如下:
function myCustomParseInt({ item, radix } = {}) {
return parseInt(item, radix);
}
通过命名参数模式,亦可跳过无需提供的参数,因函数不再依赖传递顺序。
// 位置参数需在中间插入null function greetPos(firstName, middleName, lastName) {} greetPos('Aditya', null, 'Agarwal');
// 使用命名参数,仅需提供firstName和lastName。 function greetNamed({ firstName, middleName, lastName } = {}) {} greetNamed({ firstName: 'Aditya', lastName 'Agarwal' });
总之,命名参数是一种强大模式,如今已广泛应用,但并非总需使用。有时可结合两者。浏览器fetch API用法如下:
// 以url作为位置参数的请求,以及以args做命名参数的选项。
fetch('https://google.com', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
});
// basic GET requests with just positional args
fetch('https://google.com');
此处强制参数(API路径)为位置参数,可选参数通过命名参数接收。
本文到此结束,希望对学习有所帮助,也欢迎大家支持本站。