如何使用 ES6 参数和参数

已发表: 2022-03-10
快速总结 ↬开发人员越来越多地使用 ECMAScript 6 功能,很快这些功能将不可避免。 在本教程中,您将了解 ECMAScript 6 如何升级 JavaScript 中的参数处理等。

ECMAScript 6(或 ECMAScript 2015)是 ECMAScript 标准的最新版本,显着改进了 JavaScript 中的参数处理。 我们现在可以使用休息参数、默认值和解构,以及其他新功能。

在本教程中,我们将详细探讨参数和参数,并了解 ECMAScript 6 如何升级它们。

参数与参数

参数和参数通常可以互换使用。 尽管如此,出于本教程的目的,我们将进行区分。 在大多数标准中,参数(或形式参数)是在函数声明中给出的,而实参(或实际参数)是传递给函数的。 考虑这个函数:

 function foo(param1, param2) { // do something } foo(10, 20);

在这个函数中, param1param2是函数参数,传递给函数的值( 1020 )是参数。

扩展运算符 (…)

在 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运算符 ( || )。 这个操作符检查它的第一个参数:如果它是真的,操作符返回它; 如果不是,则运算符返回其第二个参数。

这种方式在函数中很常用,但是有一个缺陷。 传递0null也会触发默认值,因为这些被认为是虚假值。 因此,如果我们确实需要将0null传递给该函数,我们将需要另一种方法来检查参数是否丢失:

 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

在此函数中,检查传递参数的类型以确保在分配默认值之前未定义它们。 这种方法只需要更多的代码,但它是一种更安全的选择,允许我们将0null传递给函数。

ECMAScript 6 中的默认参数

使用 ECMAScript 6,我们不再需要检查未定义的值来模拟默认参数。 我们现在可以将默认值直接放在function声明中:

 function foo(a = 10, b = 10) { console.log(a, b); } foo(5); // 5 10 foo(0, null); // 0 null

如您所见,省略参数会触发默认值,但不会传递0null 。 我们甚至可以使用函数来检索默认参数的值:

 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 国际