Home > php教程 > PHP开发 > Usage analysis of Arguments and Parameters in ES6

Usage analysis of Arguments and Parameters in ES6

高洛峰
Release: 2016-12-14 09:53:16
Original
1370 people have browsed it

ECMAScript 6 (also known as ECMAScript 2015) is the latest version of the ECMAScript standard, which significantly improves the way parameters are handled in JS. In addition to other new features, we can also use rest parameters, default values, destructuring assignments, etc.

In this tutorial, we will explore arguments and parameters in detail and see how ES6 improves them.

Compare Arguments and Parameters Link

Usually when Arguments and Parameters are mentioned, they are considered to be used interchangeably. However, for the purposes of this tutorial, we make a clear distinction. In most standards, parameters (formal parameters) refer to the parameters used when declaring the function name and function body, while arguments (actual parameters) refer to the certain values ​​passed in when the function is actually called. Think about the following function:

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

In this function, param1 and param2 are the formal parameters (formal parameters) of the function, and when the function foo is called, the passed in ( 10 and 20 ) are the actual parameters (actual parameters).

Extension Operator Link

In ES5, you can easily pass an array to a function using the apply() method. For example, we often use it in conjunction with Math.max() to obtain the maximum value in an array. Please look at the code below:

var myArray = [5, 10, 50];
Math.max(myArray);    // Error: NaN
Math.max.apply(Math, myArray);    // 50
Copy after login

Math.max() method does not support passing in arrays, it only accepts numbers. So when we pass array as parameter to it, error will be thrown. However, with the apply() method, the array will be converted into individual numbers, which can be processed by Math.max().

Thankfully, ES6 introduced the spread operator, and we no longer need to use the apply() method. Through the expansion operator, we can easily pass in multiple parameters for the expression:

var myArray = [5, 10, 50];
Math.max(...myArray);    // 50
Copy after login

Here, the expansion operator expands myArray into independent values ​​and passes them to the function. Using apply() in ES5 to imitate operators can achieve the goal, but the syntax is confusing and lacks the flexibility of extended operators. The spread operator is not only easy to use, but also covers many other features. For example, it can be used multiple times and mixed with other parameters in function calls:

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
Copy after login

Another advantage of the spread operator is that it can be easily used with constructors:

new Date(...[2016, 5, 6]);    // Mon Jun 06 2016 00:00:00 GMT-0700 (Pacific Daylight Time)`
Copy after login

Of course, We can rewrite the above code in ES5, but we need to use a complex pattern to avoid type errors:

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)
Copy after login

Browser support for spreading operators called in functions Link

Desktop browsers;

浏览器对Rest参数的支持情况 Link

Mobile browser:

浏览器对Rest参数的支持情况 Link

Rest parameter Link

The rest parameter and the expansion operator have the same syntax. The difference is that the rest parameter collects all parameters and converts them into an array, while the expansion operator is Expand the array into individual parameters.

function myFunction(...options) {     return options;
}
myFunction('a', 'b', 'c');      // ["a", "b", "c"]
Copy after login

If no actual parameters are passed in when the function is called, the rest parameter will output an empty array, as follows:

function myFunction(...options) {
     return options;
}myFunction();      // []
Copy after login

The rest parameter is especially useful when creating a variable function (that is, a function with a variable number of parameters) . Rest parameters have the inherent advantage of arrays and can quickly replace arguments objects (this term will be explained below). The following function is written in ES5, let’s take a look:

function checkSubstrings(string) {
  for (var i = 1; i < arguments.length; i++) {
    if (string.indexOf(arguments[i]) === -1) {
      return false;
    }
  }
  return true;
}
checkSubstrings(&#39;this is a string&#39;, &#39;is&#39;, &#39;this&#39;);   // true
Copy after login

This function checks whether the string ( this is a string ) includes these substrings ( is , this ). The first problem with this function is that we must see if there are multiple parameters in the function body. The second problem is that the loop must start from 1, not from 0, because arguments[0] points to the first parameter (this is a string). If later we want to add another parameter before or after this string, we may forget to update the loop body. By using the rest parameter, we can easily avoid these problems:

function checkSubstrings(string, ...keys) {
  for (var key of keys) {
    if (string.indexOf(key) === -1) {
      return false;
    }
  }
  return true;
}
checkSubstrings(&#39;this is a string&#39;, &#39;is&#39;, &#39;this&#39;);   // true
Copy after login

The output of this function is the same as the output of the previous function. Let me mention here again that the parameter string is included in the argument of this function, and the first one is passed in, and the remaining parameters are put into an array and assigned to the variable named keys.

Use rest parameters instead of arguments objects to improve the readability of the code and avoid some js optimization issues 1. However, rest parameters are not without their drawbacks. For example, it must be the last parameter, otherwise an error will be reported, as shown in the following function:

function logArguments(a, ...params, b) {
        console.log(a, params, b);
}
logArguments(5, 10, 15);    // SyntaxError: parameter after rest parameter
Copy after login

Another disadvantage is that a function declaration can only allow one rest parameter:

function logArguments(...param1, ...param2) {
}
logArguments(5, 10, 15);    // SyntaxError: parameter after rest parameter
Copy after login

Browser support for Rest parameters Link

Desktop Browser:

浏览器对Rest参数的支持情况 Link

移动端浏览器:

浏览器对Rest参数的支持情况 Link

默认参数 Link

ES5 默认参数 Link

在ES5中,JS 并不支持默认参数, 但是,我们也有一种变通的方案,那就是在函数中使用 OR 操作符( || )。我们简单地模仿ES5中的默认参数,请看下面函数:

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
Copy after login

该函数预期传入两个参数,但如果在调用该函数时,没有传入实参,则它会用默认值。在函数体内,如果没有传入实际参数,则会被自动设为 undefined, 所以,我们可以检测这些参数,并且声明他们的默认值。我们可以使用 OR 操作符( || )来检测是否有传入实际参数,并且设定他们的默认值。 OR 操作符会检测它的第一个参数,如果有 实际值 2 ,则用第一个,如果没有,则用它的第二个参数。

这种方法在函数中普遍使用,但它有一个瑕疵,那就是传入 0 或者 null 也会触发默认值,因为 0 和 null 都被认为是false. 所以,如果我们需要给函数传入 0 和 null 时,我们需要另一种方式去检测这个参数是否缺失:

function foo(param1, param2) { 
 if(param1 === undefined){
    param1 = 10;
  }  if(param2 === undefined){
    param2 = 10;
  }  console.log(param1, param2);
}
foo(0, null);    // 0, nullfoo();    // 10, 10
Copy after login

在上面这个函数中,只有当所传的参数全等于 undefined 时,才会使用默认值。这种方式需要用到的代码稍微多点,但是安全度更高,我们可以给函数传入 0 和 null。

ES6 默认参数 Link

有了ES6,我们不需要再去检测哪些值为undefined并且给它们设定默认值了。现在我们可以直接在函数声明中放置默认值:

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

如你所见,省略一个参数,就会触发一个默认值,但是传入 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)
Copy after login

注意 getParam 这个函数只有在第二个参数省略时才会被调用。所以当我们给 multiply 传入两个参数并调用它时,alert是不会出现的。

默认参数还有一个有趣的特性,那就是我们可以在函数声明中指定其它参数和变量的值:

function myFunction(a=10, b=a) {
     console.log(&#39;a = &#39; + a + &#39;; b = &#39;  + b);
}
myFunction();     // a=10; b=10
myFunction(22);    // a=22; b=22
myFunction(2, 4);    // a=2; b=4
Copy after login

你甚至可以在函数声明中做运算:

function myFunction(a, b = ++a, c = a*b) {
     console.log(c);
}
myFunction(5);    // 36
Copy after login

请注意,JavaScript 和其它语言不同, 它是在函数被调用时,才去求参数的默认值。

function add(value, array = []) {
  array.push(value);
  return array;
}
add(5);    // [5]
add(6);    // [6], not [5, 6]
Copy after login

浏览器对默认参数的支持情况 Link

桌面浏览器:

浏览器对Rest参数的支持情况 Link

移动端浏览器:

浏览器对Rest参数的支持情况 Link

解构赋值 Link

解构赋值是ES6的新特性。我们可以从数组和对象中提取值,对变量进行赋值。这种语法清晰且易于理解,尤其是在给函数传参时特别有用。

在ES5里面,我们经常用一个配置对象来处理大量的可选参数, 特别是当对象属性的顺序可变时:

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: &#39;http&#39;,
  port: 800,
  delay: 150,
  retries: 10,
  timeout: 500,
  log: true};
initiateTransfer(options);
Copy after login

这种方式实现起来很好,已经被许多JS开发者所采用。 只是我们必须看函数内部,才知道函数预期需要哪些参数。结合解构赋值,我们就可以在函数声明中清晰地表示这些参数:

function initiateTransfer({protocol, port, delay, retries, timeout, log}) {
     // code to initiate transfer
};
var options = {
  protocol: &#39;http&#39;,
  port: 800,
  delay: 150,
  retries: 10,
  timeout: 500,
  log: true
}
initiateTransfer(options);
Copy after login

在该函数中,我们没有传入一个配置对象,而是以对象解构赋值的方式,给它传参数。这样做不仅使这个函数更加简明,可读性也更高。

我们也可以把解构赋值传参和其它规则的参数一起使用:

function initiateTransfer(param1, {protocol, port, delay, retries, timeout, log}) {
     // code to initiate transfer
}
initiateTransfer(&#39;some value&#39;, options);
Copy after login

注意如果函数调用时,参数被省略掉,则会抛出错误,如下:

function initiateTransfer({protocol, port, delay, retries, timeout, log}) {
     // code to initiate transfer
}
initiateTransfer();  // TypeError: Cannot match against &#39;undefined&#39; or &#39;null&#39;
Copy after login

当我们需要让参数都是必填时,这种方法能够得到我们想要的结果,但如果我们希望参数是可选的时候呢?想要让参数缺失时不会报错,我们就需要给默认参数设定一个默认值:

function initiateTransfer({protocol, port, delay, retries, timeout, log} = {}) {
     // code to initiate transfer
}
initiateTransfer();    // no error
Copy after login

上面这个函数中,需要解构赋值的参数有了一个默认值,这个默认值就是一个空对象。这时候,函数被调用时,即使没有传入参数,也不会报错了。

我们也可以给每个被解构的参数设定默认值,如下:

function initiateTransfer({
    protocol = &#39;http&#39;,
    port = 800,
    delay = 150,
    retries = 10,
    timeout = 500,
    log = true
}) {     // code to initiate transfer
}
Copy after login

在这个例子中,每个属性都有一个默认值,我们不需要手动去检查哪个参数值是 undefined ,然后在函数中给它设定默认值了。

浏览器对解构赋值的支持情况 Link

桌面浏览器:

浏览器对Rest参数的支持情况 Link

移动端浏览器:

浏览器对Rest参数的支持情况 Link

参数传递 Link

参数能通过 引用 或 值 传递给函数。修改按引用传递的参数,一般反映在全局中,而修改按值传递的参数,则只是反映在函数内部。

在像 Visual Basic 和 PowerShell 这样的语言中,我们可以选择是按引用还是按值来传递参数,但是在 JavaScript 中则不行。

按值传递参数 Link

从技术上来讲,JavaScript 只允许按值传参。当我们给函数按值传递一个参数时,该函数的作用域内就已经复制了这个值。因此,这个值的改变,只会在函数内部反映出来。请思考下面这个列子:

var a = 5;function increment(a) {
    a = ++a;
    console.log(a);
}
increment(a);   // 6
console.log(a);    // 5
Copy after login

这里,修改函数里面的参数 a = ++a ,是不会影响到原来 a 的值。 所以在函数外面打印 a 的值,输出仍然是 5 。

按引用传递参数 Link

在JavaScript中,一切都是按值传递的。但当我们给函数传一个变量,而这个变量所指向的是一个对象(包括数组)时,这个 变量 就是对象的一个引用。通过这个变量来改变对象的属性值,是会从根本上改变这个对象的。

来看下面这个例子:

function foo(param){
    param.bar = &#39;new value&#39;;
}
obj = {
    bar : &#39;value&#39;
}
console.log(obj.bar);   // value
foo(obj);
console.log(obj.bar);   // new value
Copy after login

如你所见,对象的属性值在函数内部被修改了,被修改的值在函数外面也是可见的。

当我们传递一个没有初始值的参数时,如数组或对象,会隐形地创建了一个变量,这个变量指向记忆中原对象所在的位置。这个变量随后被传递给了函数,在函数内部对这个变量进行修改将会影响到原对象。

参数类型检测、参数缺失或参数多余 Link

在强类型语言中,我们必须在函数声明中明确参数的类型,但是 JavaScript 没有这种特性。在JavaScript中,我们传递给函数的参数个数不限,也可以是任何类型的数据。

假设现在有一个函数,这个函数只接受一个参数。但是当函数被调用时,它本身没有限制传入的参数只能是一个,我们可以随意地传入一个、两个、甚至是更多。我们也可以什么都不传,这样都不会报错。

形参( arguments )和 实参( parameters )的个数不同有两种情况:

实参少于形参

缺失的参数都会等同于 undefined 。

实参多于形参

多余的参数都将被忽略,但它们会以数组的形式保存于变量 arguments 中(下文会讨论到)。

必填参数 Link

如果一个参数在函数调用时缺失了,它将被设为 undefined 。基于这一点,我们可以在参数缺失时抛出一个错误:

function foo(mandatory, optional) {
    if (mandatory === undefined) {
        throw new Error(&#39;Missing parameter: mandatory&#39;);
    }
}
Copy after login

在 ES6 中,我们可以更好地利用这个特性,使用默认参数来设定必填参数:

function throwError() {
    throw new Error(&#39;Missing parameter&#39;);
}
function foo(param1 = throwError(), param2 = throwError()) {
    // do something
}
foo(10, 20);    // ok
foo(10);   // Error: missing parameter
Copy after login

参数对象 Link

为了取代参数对象,rest参数在 ECMAScript 4 中就已经得到支持,但是 ECMAScript 4 没有落实。随着 ECMAScript 6 版本的发布,JS 正式支持rest参数。它也拟定计划,准备 抛弃 对参数对象 arguments object 的支持。

参数对象是一个类数组对象,可在一切函数内使用。它允许通过数字而不是名称,来找回被传递给函数的参数值。这个对象使得我们可以给函数传递任何参数。思考以下代码段:

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);
Copy after login

该函数预期接收一个参数。但是当我们给它传入两个参数并且调用它时,第一个参数通过名为 param1 的形参或者参数对象 arguments[0] 被函数所接受,而第二个参数只能被放在 argument[1] 里面。此外,请注意,参数对象可以与命名参数一起使用。

参数对象给每个被传递给函数的参数提供了一个入口,并且第一个入口的下标从 0开始。如果我们要给上面这个函数传递更多的参数,我们可以写 arguments[2] , arguments[3] 等等。

我们甚至可以跳过设定命名参数这一步,直接使用参数对象:

function checkParams() {
    console.log(arguments[1], arguments[0], arguments[2]);
}
checkParams(2, 4, 6);  // 4 2 6
Copy after login

事实上,命名参数只是为了方便使用,并不是必须的。类似地,rest参数也可用于反映被传递的参数:

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);
Copy after login

参数对象是一个类数组的对象,只是它没有数组本身具备的方法,如 slice() 和 foreach() 。 如果要在参数对象中使用数组的方法,首先要把它转换成一个真正的数组。

function sort() {
    var a = Array.prototype.slice.call(arguments);
    return a.sort();
}
sort(40, 20, 50, 30);    // [20, 30, 40, 50]
Copy after login

在该函数中,采用了 Array.prototype.slice.call() 来快速地把参数对象转换成一个数组。接着,在 sort() 方法中,为这个数组排序并且把它返回。

ES6 新增了更直接的方法,用 Array.from() 把任何类数组对象转换成数组:

function sort() {
    var a = Array.from(arguments);
    return a.sort();
}
sort(40, 20, 50, 30);    // [20, 30, 40, 50]
Copy after login

长度属性 Link

尽管参数对象从技术上来讲,不算是一个数组,但仍有一个长度属性,来检测传递给函数的参数个数:

function countArguments() {
    console.log(arguments.length);
}
countArguments();    // 0
countArguments(10, null, "string");    // 3
Copy after login

通过 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
    }
}
Copy after login

rest参数是数组,所以他们都有 length 属性。 所以上面的代码,在ES6里面可以用rest参数写成下面这样:

function foo(...params) {
  if (params.length < 2) {
        throw new Error("This function expects at least two arguments");
    } else if (params.length === 2) {
        // do something
    }
}
Copy after login

被调用属性与调用属性 Link

被调用 属性指向当前正在执行的函数,而 调用 属性则指向那个调用了 当前正在执行的函数 的函数。 在ES5的严格模式下,这些属性是不被支持的,如果尝试使用它们,则会报错。

arguments.callee 这个属性在递归函数中很有用,尤其在匿名函数中。因为匿名函数没有名称,只能通过 arguments.callee 来指向它。

var result = (function(n) {
  if (n
Copy after login

严格模式和非严格模式下的参数对象 Link

在ES5非严格模式下, 参数对象 有个不一般的特性:它能使 自身的值 跟 与之相对应的命名参数的值 保持同步。

请看下面这个例子:

function foo(param) {   console.log(param === arguments[0]);    // true
   arguments[0] = 500;   console.log(param === arguments[0]);    // true
   return param
}
foo(200);    // 500
Copy after login

在这个函数里面, arguments[0] 被重新赋值为 500 。由于 arguments 的值总是和对应的命名参数保持同步,所以改变了 arguments[0] 的值,也就相应的改变了param 的值。实际上,他们就像是同一个变量,拥有两个不同的名字而已。而在 ES5严格模式 下,参数对象的这种特性则被移除了。

"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
Copy after login

加上 严格模式 , 现在改变 arguments[0] 的值是不会影响到 param 的值了,打印出来的值也跟预期的一致。 在 ES6 中 该函数的输出跟在 ES5 严格模式下是一样的。需要记住的是,当函数声明中使用了默认值时,参数对象是不会受到影响的:

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(&#39;string1&#39;, &#39;string2&#39;);
Copy after login

在这个函数中,尽管 param3 有默认值 20 ,但是 arguments[2] 仍然是undefined , 因为函数调用时只传了两个值。换言之,设定默认值对参数对象是没有任何影响的。

总结 Link

ES6 给 JS 带来了上百个大大小小的改进。 越来越多的开发者正使用ES6的新特性, 所以我们都需要去了解它们。在本教程中,我们学习了ES6是如何改善JS的参数处理的,但我们仍只是知晓了ES6的皮毛。更多新的、有趣的特性值得我们去探讨。


Related labels:
source:php.cn
Statement of this Website
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn
Popular Recommendations
Popular Tutorials
More>
Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template