如何使用 ES6 参数和参数
已发表: 2022-03-10ECMAScript 6(或 ECMAScript 2015)是 ECMAScript 标准的最新版本,显着改进了 JavaScript 中的参数处理。 我们现在可以使用休息参数、默认值和解构,以及其他新功能。
在本教程中,我们将详细探讨参数和参数,并了解 ECMAScript 6 如何升级它们。
参数与参数
参数和参数通常可以互换使用。 尽管如此,出于本教程的目的,我们将进行区分。 在大多数标准中,参数(或形式参数)是在函数声明中给出的,而实参(或实际参数)是传递给函数的。 考虑这个函数:
function foo(param1, param2) { // do something } foo(10, 20);
在这个函数中, param1
和param2
是函数参数,传递给函数的值( 10
和20
)是参数。
扩展运算符 (…)
在 ECMAScript 5 中, apply()
方法是一种方便的工具,可以将数组作为参数传递给函数。 例如,它通常与Math.max()
方法一起用于查找数组中的最大值。 考虑这个代码片段:
var myArray = [5, 10, 50]; Math.max(myArray); // Error: NaN Math.max.apply(Math, myArray); // 50
Math.max()
方法不支持数组; 它只接受数字。 将数组传递给Math.max()
函数时,会引发错误。 但是当使用apply()
方法时,数组作为单独的数字发送,因此Math.max()
方法可以处理它。
幸运的是,随着 ECMAScript 6 中扩展运算符的引入,我们不再需要使用apply()
方法。 使用扩展运算符,我们可以轻松地将表达式扩展为多个参数:
var myArray = [5, 10, 50]; Math.max(...myArray); // 50
在这里,展开运算符扩展myArray
以为函数创建单独的值。 虽然在 ECMAScript 5 中使用apply()
模拟扩展运算符是可能的,但语法令人困惑并且缺乏扩展运算符的灵活性。 扩展运算符不仅更易于使用,而且包含更多功能。 例如,它可以多次使用,并且可以在function
调用中与其他参数混合使用:
function myFunction() { for(var i in arguments){ console.log(arguments[i]); } } var params = [10, 15]; myFunction(5, ...params, 20, ...[25]); // 5 10 15 20 25
扩展运算符的另一个优点是它可以很容易地与构造函数一起使用:
new Date(...[2016, 5, 6]); // Mon Jun 06 2016 00:00:00 GMT-0700 (Pacific Daylight Time)
当然,我们可以在 ECMAScript 5 中重写前面的代码,但是我们需要使用复杂的模式来避免出现类型错误:
new Date.apply(null, [2016, 4, 24]); // TypeError: Date.apply is not a constructor new (Function.prototype.bind.apply(Date, [null].concat([2016, 5, 6]))); // Mon Jun 06 2016 00:00:00 GMT-0700 (Pacific Daylight Time)
在函数调用中扩展运算符浏览器支持
桌面浏览器:
铬合金 | 火狐 | IE浏览器 | 微软边缘 | 歌剧 | 苹果浏览器 |
---|---|---|---|---|---|
46 | 27 | – | 支持的 | – | 7.1 |
移动浏览器:
安卓版 Chrome | 火狐手机 | Safari 移动版 | 歌剧移动 | IE手机 |
---|---|---|---|---|
46 | 27 | 8 | – | – |
休息参数
rest 参数与扩展运算符具有相同的语法,但它不是将数组扩展为参数,而是收集参数并将它们转换为数组。
function myFunction(...options) { return options; } myFunction('a', 'b', 'c'); // ["a", "b", "c"]
如果没有参数,则其余参数将设置为空数组:
function myFunction(...options) { return options; } myFunction(); // []
在创建可变参数函数(接受可变数量参数的函数)时,rest 参数特别有用。 作为数组的好处,剩余参数可以很容易地替换arguments
对象(我们将在本教程后面解释)。 考虑这个用 ECMAScript 5 编写的函数:
function checkSubstrings(string) { for (var i = 1; i < arguments.length; i++) { if (string.indexOf(arguments[i]) === -1) { return false; } } return true; } checkSubstrings('this is a string', 'is', 'this'); // true
此函数检查字符串是否包含多个子字符串。 这个函数的第一个问题是我们必须在function
体内部查看它是否有多个参数。 第二个问题是迭代必须从1
而不是0
开始,因为arguments[0]
指向第一个参数。 如果我们稍后决定在字符串之前或之后添加另一个参数,我们可能会忘记更新循环。 使用其余参数,我们很容易避免这些问题:
function checkSubstrings(string, ...keys) { for (var key of keys) { if (string.indexOf(key) === -1) { return false; } } return true; } checkSubstrings('this is a string', 'is', 'this'); // true
此函数的输出与前一个相同。 同样,参数string
填充了首先传递的参数,但其余参数放入数组中并分配给变量keys
。
使用 rest 参数而不是arguments
对象可以提高代码的可读性并避免 JavaScript 中的优化问题。 然而,其余参数并非没有限制。 例如,它必须是最后一个参数; 否则会出现语法错误:
function logArguments(a, ...params, b) { console.log(a, params, b); } logArguments(5, 10, 15); // SyntaxError: parameter after rest parameter
另一个限制是function
声明中只允许有一个剩余参数:
function logArguments(...param1, ...param2) { } logArguments(5, 10, 15); // SyntaxError: parameter after rest parameter
休息参数浏览器支持
桌面浏览器:
铬合金 | 火狐 | IE浏览器 | 微软边缘 | 歌剧 | 苹果浏览器 |
---|---|---|---|---|---|
47 | 15 | – | 支持的 | 34 | – |
移动浏览器:
安卓版 Chrome | 火狐手机 | Safari 移动版 | 歌剧移动 | IE手机 |
---|---|---|---|---|
47 | 15 | – | – | – |
默认参数
ECMAScript 5 中的默认参数
JavaScript 不支持 ECMAScript 5 中的默认参数,但有一个简单的解决方法。 在函数内部使用逻辑OR
运算符 ( ||
),我们可以轻松模拟 ECMAScript 5 中的默认参数。考虑这个函数:
function foo(param1, param2) { param1 = param1 || 10; param2 = param2 || 10; console.log(param1, param2); } foo(5, 5); // 5 5 foo(5); // 5 10 foo(); // 10 10
这个函数需要两个参数,但是当它在没有参数的情况下被调用时,它将使用默认值。 在函数内部,缺少的参数会自动设置为未定义; 因此,我们可以检测这些参数并为它们声明默认值。 为了检测缺失的参数并设置默认值,我们使用逻辑OR
运算符 ( ||
)。 这个操作符检查它的第一个参数:如果它是真的,操作符返回它; 如果不是,则运算符返回其第二个参数。
这种方式在函数中很常用,但是有一个缺陷。 传递0
或null
也会触发默认值,因为这些被认为是虚假值。 因此,如果我们确实需要将0
或null
传递给该函数,我们将需要另一种方法来检查参数是否丢失:
function foo(param1, param2) { if(param1 === undefined){ param1 = 10; } if(param2 === undefined){ param2 = 10; } console.log(param1, param2); } foo(0, null); // 0, null foo(); // 10, 10
在此函数中,检查传递参数的类型以确保在分配默认值之前未定义它们。 这种方法只需要更多的代码,但它是一种更安全的选择,允许我们将0
和null
传递给函数。
ECMAScript 6 中的默认参数
使用 ECMAScript 6,我们不再需要检查未定义的值来模拟默认参数。 我们现在可以将默认值直接放在function
声明中:
function foo(a = 10, b = 10) { console.log(a, b); } foo(5); // 5 10 foo(0, null); // 0 null
如您所见,省略参数会触发默认值,但不会传递0
或null
。 我们甚至可以使用函数来检索默认参数的值:
function getParam() { alert("getParam was called"); return 3; } function multiply(param1, param2 = getParam()) { return param1 * param2; } multiply(2, 5); // 10 multiply(2); // 6 (also displays an alert dialog)
请注意,只有在省略第二个参数时才会调用getParam
函数。 因此,当我们使用两个参数调用multiply()
函数时,不会显示警报。
默认参数的另一个有趣特性是我们可以在function
声明中引用其他参数和变量:
function myFunction(a=10, b=a) { console.log('a = ' + a + '; b = ' + b); } myFunction(); // a=10; b=10 myFunction(22); // a=22; b=22 myFunction(2, 4); // a=2; b=4
您甚至可以在function
声明中执行操作:
function myFunction(a, b = ++a, c = a*b) { console.log(c); } myFunction(5); // 36
请注意,与其他一些语言不同,JavaScript 在调用时评估默认参数:
function add(value, array = []) { array.push(value); return array; } add(5); // [5] add(6); // [6], not [5, 6]
默认参数浏览器支持
桌面浏览器:
特征 | 铬合金 | 火狐 | IE浏览器 | 微软边缘 | 歌剧 | 苹果浏览器 |
---|---|---|---|---|---|---|
基本支持 | 49 | 15 | – | 14 | – | – |
默认参数后没有默认值的参数 | 49 | 26 | – | 14 | – | – |
移动浏览器:
特征 | 安卓版 Chrome | 火狐手机 | Safari 移动版 | 歌剧移动 | IE手机 |
---|---|---|---|---|---|
基本支持 | 49 | 15 | – | – | – |
默认参数后没有默认值的参数 | 46 | 26 | – | – | – |
解构
解构是 ECMAScript 6 中的一个新特性,它使我们能够从数组和对象中提取值,并使用类似于对象和数组字面量的语法将它们分配给变量。 语法清晰易懂,在将参数传递给函数时特别有用。
在 ECMAScript 5 中,配置对象通常用于处理大量可选参数,尤其是在属性顺序无关紧要的情况下。 考虑这个函数:
function initiateTransfer(options) { var protocol = options.protocol, port = options.port, delay = options.delay, retries = options.retries, timeout = options.timeout, log = options.log; // code to initiate transfer } options = { protocol: 'http', port: 800, delay: 150, retries: 10, timeout: 500, log: true }; initiateTransfer(options);
这种模式是 JavaScript 开发人员常用的,它运行良好,但我们必须查看function
体内部,看看它需要什么参数。 使用解构参数,我们可以在function
声明中清楚的指明参数:
function initiateTransfer({protocol, port, delay, retries, timeout, log}) { // code to initiate transfer }; var options = { protocol: 'http', port: 800, delay: 150, retries: 10, timeout: 500, log: true } initiateTransfer(options);
在这个函数中,我们使用了对象解构模式,而不是配置对象。 这使我们的函数不仅更简洁,而且更易于阅读。
我们还可以将解构参数与常规参数组合:
function initiateTransfer(param1, {protocol, port, delay, retries, timeout, log}) { // code to initiate transfer } initiateTransfer('some value', options);
请注意,如果在function
调用中省略参数,将引发类型错误:
function initiateTransfer({protocol, port, delay, retries, timeout, log}) { // code to initiate transfer } initiateTransfer(); // TypeError: Cannot match against 'undefined' or 'null'
当我们需要参数时,这是所需的行为,但是如果我们希望它们是可选的呢? 为了防止参数丢失时出现此错误,我们需要为解构参数分配一个默认值:
function initiateTransfer({protocol, port, delay, retries, timeout, log} = {}) { // code to initiate transfer } initiateTransfer(); // no error
在此函数中,提供了一个空对象作为解构参数的默认值。 现在,如果在没有任何参数的情况下调用此函数,则不会发生错误。
我们还可以为每个解构参数分配一个默认值:
function initiateTransfer({ protocol = 'http', port = 800, delay = 150, retries = 10, timeout = 500, log = true }) { // code to initiate transfer }
在此示例中,每个属性都有一个默认参数,无需我们手动检查未定义的参数并在function
体内分配默认值。
解构浏览器支持
桌面浏览器:
特征 | 铬合金 | 火狐 | IE浏览器 | 微软边缘 | 歌剧 | 苹果浏览器 |
---|---|---|---|---|---|---|
基本支持 | 49 | 2.0 | – | 14 | – | 7.1 |
具有默认值分配的解构参数 | 49 | 47 | – | 14 | – | – |
移动浏览器:
特征 | 安卓版 Chrome | 火狐手机 | Safari 移动版 | 歌剧移动 | IE手机 |
---|---|---|---|---|---|
基本支持 | 49 | 1 | 8 | – | – |
默认参数后没有默认值的参数 | 49 | 47 | – | – | – |
传递参数
将参数传递给函数有两种方法:通过引用或通过值。 修改通过引用传递的参数会全局反映,但修改通过值传递的参数仅反映在函数内部。
在某些语言中,例如 Visual Basic 和 PowerShell,我们可以选择指定是按引用还是按值传递参数,但 JavaScript 并非如此。
按值传递参数
从技术上讲,JavaScript 只能按值传递。 当我们通过值将参数传递给函数时,会在function
范围内创建该值的副本。 因此,对值的任何更改仅反映在function
内部。 考虑这个例子:
var a = 5; function increment(a) { a = ++a; console.log(a); } increment(a); // 6 console.log(a); // 5
在这里,修改函数内部的参数对原始值没有影响。 因此,当从函数外部记录变量时,打印的值仍然是5
。
通过引用传递参数
在 JavaScript 中,一切都是按值传递的,但是当我们传递一个引用对象(包括数组)的变量时,“值”是对该对象的引用,并且更改变量引用的对象的属性确实会改变底层对象。
考虑这个函数:
function foo(param){ param.bar = 'new value'; } obj = { bar : 'value' } console.log(obj.bar); // value foo(obj); console.log(obj.bar); // new value
如您所见,对象的属性在函数内部被修改,但修改后的值在函数外部可见。
当我们传递诸如数组或对象之类的非原始值时,会在后台创建一个变量,该变量指向内存中原始对象的位置。 然后将此变量传递给函数,修改它会影响原始对象。
类型检查和缺少或额外的参数
在强类型语言中,我们必须在function
声明中指定参数的类型,但是 JavaScript 缺少这个特性。 在 JavaScript 中,我们传递给函数的数据类型或参数数量并不重要。
假设我们有一个只接受一个参数的函数。 当我们调用该函数时,我们不仅限于将一个参数传递给函数; 我们可以自由地传递一个、两个或更多参数! 我们甚至可以选择什么都不通过,这样就不会发生错误。
自变量和参数的数量可以通过两种方式有所不同:
- 参数少于参数。
缺少的参数将等于undefined
。 - 参数多于参数。
额外的参数将被忽略,但可以通过特殊的类似数组的变量参数检索(接下来讨论)。
强制参数
如果function
调用中缺少参数,它将被设置为undefined
。 如果省略参数,我们可以利用这种行为并抛出错误:
function foo(mandatory, optional) { if (mandatory === undefined) { throw new Error('Missing parameter: mandatory'); } }
在 ECMAScript 6 中,我们可以更进一步,使用默认参数来设置强制参数:
function throwError() { throw new Error('Missing parameter'); } function foo(param1 = throwError(), param2 = throwError()) { // do something } foo(10, 20); // ok foo(10); // Error: missing parameter
参数对象
为了替换arguments
对象,ECMAScript 4 添加了对 rest 参数的支持,但 ECMAScript 4 从未实现。 随着 ECMAScript 6 的发布,JavaScript 现在正式支持其余参数。 它还取消了放弃对arguments
对象的支持的计划。
arguments
对象是一个在所有函数中都可用的类数组对象。 它允许通过数字而不是名称检索传递给函数的argument
值。 该对象允许我们将任意数量的参数传递给函数。 考虑以下代码片段:
function checkParams(param1) { console.log(param1); // 2 console.log(arguments[0], arguments[1]); // 2 3 console.log(param1 + arguments[0]); // 2 + 2 } checkParams(2, 3);
此函数预计只接收一个参数。 当我们用两个参数调用它时,第一个参数可以通过参数名称param1
或参数对象arguments[0]
在函数中访问,但第二个参数只能作为arguments[1]
访问。 另外,请注意arguments
对象可以与命名参数一起使用。
arguments
对象包含传递给函数的每个参数的条目,第一个条目的索引从0
开始。 如果我们想在上面的示例中访问更多参数,我们将编写arguments[2]
, arguments[3]
等等。
我们甚至可以完全跳过设置命名参数而只使用arguments
对象:
function checkParams() { console.log(arguments[1], arguments[0], arguments[2]); } checkParams(2, 4, 6); // 4 2 6
事实上,命名参数是一种方便,而不是必需品。 同样,其余参数可用于反映传递的参数:
function checkParams(...params) { console.log(params[1], params[0], params[2]); // 4 2 6 console.log(arguments[1], arguments[0], arguments[2]); // 4 2 6 } checkParams(2, 4, 6);
arguments
对象是一个类似数组的对象,但它缺少诸如slice()
和foreach()
之类的数组方法。 为了在arguments
对象上使用数组方法,首先需要将对象转换为真正的数组:
function sort() { var a = Array.prototype.slice.call(arguments); return a.sort(); } sort(40, 20, 50, 30); // [20, 30, 40, 50]
在此函数中, Array.prototype.slice.call()
用作将arguments
对象转换为数组的快速方法。 接下来, sort()
方法对数组中的项进行排序并返回它。
ECMAScript 6 有一个更直接的方法。 Array.from()
是 ECMAScript 6 中的新增功能,它从任何类似数组的对象创建一个新数组:
function sort() { var a = Array.from(arguments); return a.sort(); } sort(40, 20, 50, 30); // [20, 30, 40, 50]
长度属性
尽管参数对象在技术上不是一个数组,但它有一个length
属性,可用于检查传递给函数的参数数量:
function countArguments() { console.log(arguments.length); } countArguments(); // 0 countArguments(10, null, "string"); // 3
通过使用length
属性,我们可以更好地控制传递给函数的参数数量。 例如,如果一个函数需要两个参数才能工作,我们可以使用length
属性来检查传递的参数的数量,如果它们少于预期,则抛出错误:
function foo(param1, param2) { if (arguments.length < 2) { throw new Error("This function expects at least two arguments"); } else if (arguments.length === 2) { // do something } }
其余参数是数组,因此它们具有length
属性。 在 ECMAScript 6 中,前面的代码可以用剩余参数重写:
function foo(...params) { if (params.length < 2) { throw new Error("This function expects at least two arguments"); } else if (params.length === 2) { // do something } }
Callee 和 Caller 属性
callee
属性是指当前正在运行的函数, caller
是指调用了当前正在执行的函数的函数。 在 ECMAScript 5 严格模式下,这些属性已被弃用,尝试访问它们会导致 TypeError。
arguments.callee
属性在递归函数中很有用(递归函数是一个通过名称引用自身的常规函数),尤其是在函数名不可用时(匿名函数)。 因为匿名函数没有名称,所以引用它的唯一方法是通过arguments.callee
。
var result = (function(n) { if (n <= 1) { return 1; } else { return n * arguments.callee(n - 1); } })(4); // 24
严格模式和非严格模式中的参数对象
在 ECMAScript 5 非严格模式下, arguments
对象有一个不寻常的特性:它保持其值与相应命名参数的值同步。
考虑以下代码片段:
function foo(param) { console.log(param === arguments[0]); // true arguments[0] = 500; console.log(param === arguments[0]); // true return param } foo(200); // 500
在此函数内部,为arguments[0]
分配了一个新值。 因为arguments
的值始终与命名参数的值保持同步,所以对arguments[0]
的更改也会更改param
的值。 实际上,它们就像同一个变量的两个不同名称。 在 ECMAScript 5 严格模式下, arguments
对象的这种令人困惑的行为已被删除:
"use strict"; function foo(param) { console.log(param === arguments[0]); // true arguments[0] = 500; console.log(param === arguments[0]); // false return param } foo(200); // 200
这一次,更改arguments[0]
不会影响param
,并且输出符合预期。 该函数在 ECMAScript 6 中的输出与在 ECMAScript 5 严格模式中相同,但请记住,在function
声明中使用默认值时, arguments
对象不受影响:
function foo(param1, param2 = 10, param3 = 20) { console.log(param1 === arguments[0]); // true console.log(param2 === arguments[1]); // true console.log(param3 === arguments[2]); // false console.log(arguments[2]); // undefined console.log(param3); // 20 } foo('string1', 'string2');
在这个函数中,即使param3
有一个默认值,它也不等于arguments[2]
,因为只有两个参数被传递给函数。 换句话说,设置默认值对arguments
对象没有影响。
结论
ECMAScript 6 为 JavaScript 带来了数百个大大小小的改进。 越来越多的开发人员正在使用 ECMAScript 6 的特性,很快这些特性将不可避免。 在本教程中,我们了解了 ECMAScript 6 如何升级 JavaScript 中的参数处理,但我们只是触及了 ECMAScript 6 的皮毛。该语言的许多其他新的和有趣的特性值得一试。
链接
- ECMAScript 6 兼容性表,Juriy Zaytsev
- “ECMAScript 2015 语言规范”,ECMA 国际