Heim  >  Artikel  >  php教程  >  Nutzungsanalyse von Argumenten und Parametern in ES6

Nutzungsanalyse von Argumenten und Parametern in ES6

高洛峰
高洛峰Original
2016-12-14 09:53:161290Durchsuche

ECMAScript 6 (auch bekannt als ECMAScript 2015) ist die neueste Version des ECMAScript-Standards, die die Art und Weise, wie Parameter in JS behandelt werden, erheblich verbessert. Neben anderen neuen Funktionen können wir auch Restparameter, Standardwerte, Destrukturierungszuweisungen usw. verwenden.

In diesem Tutorial werden wir Argumente und Parameter im Detail untersuchen und sehen, wie ES6 sie verbessert.

Link zum Vergleich von Argumenten und Parametern

Wenn Argumente und Parameter erwähnt werden, werden sie normalerweise als austauschbar betrachtet. Für die Zwecke dieses Tutorials machen wir jedoch eine klare Unterscheidung. In den meisten Standards beziehen sich Parameter (formale Parameter) auf die Parameter, die bei der Deklaration des Funktionsnamens und des Funktionskörpers verwendet werden, während sich Argumente (tatsächliche Parameter) auf bestimmte Werte beziehen, die beim tatsächlichen Aufruf der Funktion übergeben werden. Denken Sie an die folgende Funktion:

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

In dieser Funktion sind param1 und param2 die formalen Parameter (formale Parameter) der Funktion, und wenn die Funktion foo aufgerufen wird, werden die in (10 und 20) übergebenen Werte übergeben. sind die tatsächlichen Parameter (tatsächliche Parameter).

Erweiterungsoperator Link

In ES5 können Sie mit der Methode apply() ganz einfach ein Array an eine Funktion übergeben. Beispielsweise verwenden wir es häufig in Verbindung mit Math.max(), um den Maximalwert in einem Array zu ermitteln. Bitte schauen Sie sich den folgenden Code an:

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

Die Math.max()-Methode unterstützt die Übergabe von Arrays nicht, sie akzeptiert nur Zahlen. Wenn wir also ein Array als Parameter übergeben, wird ein Fehler ausgegeben. Mit der Methode apply() wird das Array jedoch in einzelne Zahlen umgewandelt, die von Math.max() verarbeitet werden können.

Glücklicherweise hat ES6 den Spread-Operator eingeführt und wir müssen die apply()-Methode nicht mehr verwenden. Durch den Erweiterungsoperator können wir problemlos mehrere Parameter für den Ausdruck übergeben:

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

Hier erweitert der Erweiterungsoperator myArray in unabhängige Werte und übergibt sie an die Funktion. Die Verwendung von apply() in ES5 zur Nachahmung von Operatoren kann das Ziel erreichen, aber die Syntax ist verwirrend und es fehlt die Flexibilität erweiterter Operatoren. Der Spread-Operator ist nicht nur einfach zu bedienen, sondern deckt auch viele weitere Funktionen ab. Beispielsweise kann er beim Aufruf einer Funktion mehrfach verwendet und mit anderen Parametern gemischt werden:

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

Ein weiterer Vorteil des Spread-Operators besteht darin, dass er problemlos mit Konstruktoren verwendet werden kann:

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

Natürlich können wir den obigen Code in ES5 umschreiben, aber wir müssen ein komplexes Muster verwenden, um Typfehler zu vermeiden:

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)

Browser arbeitet mit Erweiterungen. Unterstützung für in Funktionen aufgerufene Symbole Link

Desktop-Browser;

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

Mobile Browser:

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

Rest-Parameter-Link

Der Der Rest-Parameter und der Erweiterungsoperator haben die gleiche Syntax. Der Unterschied besteht darin, dass der Rest-Parameter alle Parameter sammelt und in ein Array umwandelt, während der Erweiterungsoperator das Array in separate Parameter erweitert.

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

Wenn beim Aufruf der Funktion keine tatsächlichen Parameter übergeben werden, gibt der Rest-Parameter ein leeres Array wie folgt aus:

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

Der Rest-Parameter wird beim Erstellen verwendet eine variable Funktion (d. h. dies ist besonders nützlich für eine Funktion mit einer variablen Anzahl von Parametern). Restparameter haben den inhärenten Vorteil von Arrays und können Argumentobjekte schnell ersetzen (dieser Begriff wird weiter unten erklärt). Die folgende Funktion ist in ES5 geschrieben, werfen wir einen Blick darauf:

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

Diese Funktion prüft, ob die Zeichenfolge (dies ist eine Zeichenfolge) diese Teilzeichenfolgen (is, this) enthält. Das erste Problem bei dieser Funktion besteht darin, dass wir prüfen müssen, ob der Funktionskörper mehrere Parameter enthält. Das zweite Problem besteht darin, dass die Schleife bei 1 und nicht bei 0 beginnen muss, da arguments[0] auf den ersten Parameter zeigt (dies ist ein String). Wenn wir später vor oder nach dieser Zeichenfolge einen weiteren Parameter hinzufügen möchten, vergessen wir möglicherweise, den Schleifenkörper zu aktualisieren. Durch die Verwendung des Rest-Parameters können wir diese Probleme leicht vermeiden:

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

Die Ausgabe dieser Funktion ist dieselbe wie die Ausgabe der vorherigen Funktion. Lassen Sie mich hier noch einmal erwähnen, dass die Parameterzeichenfolge im Argument dieser Funktion enthalten ist, der erste übergeben wird und die restlichen Parameter in ein Array eingefügt und der Variablen namens „keys“ zugewiesen werden.

Verwenden Sie Restparameter anstelle von Argumentobjekten, um die Lesbarkeit des Codes zu verbessern und einige Probleme bei der JS-Optimierung zu vermeiden 1. Allerdings sind Restparameter nicht ohne Nachteile. Beispielsweise muss es der letzte Parameter sein, sonst wird ein Fehler gemeldet, wie in der folgenden Funktion gezeigt:

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

Ein weiterer Nachteil besteht darin, dass eine Funktionsdeklaration nur einen Restparameter zulassen kann:

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

Browserunterstützung für Rest-Parameter 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

该函数预期传入两个参数,但如果在调用该函数时,没有传入实参,则它会用默认值。在函数体内,如果没有传入实际参数,则会被自动设为 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

在上面这个函数中,只有当所传的参数全等于 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

如你所见,省略一个参数,就会触发一个默认值,但是传入 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 传入两个参数并调用它时,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

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

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]

浏览器对默认参数的支持情况 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);

这种方式实现起来很好,已经被许多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);

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

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

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

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

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

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

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

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

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

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

在这个例子中,每个属性都有一个默认值,我们不需要手动去检查哪个参数值是 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

这里,修改函数里面的参数 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

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

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

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

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

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

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

实参少于形参

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

实参多于形参

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

必填参数 Link

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

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

在 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

参数对象 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);

该函数预期接收一个参数。但是当我们给它传入两个参数并且调用它时,第一个参数通过名为 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

事实上,命名参数只是为了方便使用,并不是必须的。类似地,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);

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

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() 来快速地把参数对象转换成一个数组。接着,在 sort() 方法中,为这个数组排序并且把它返回。

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

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

长度属性 Link

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

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
    }
}

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
    }
}

被调用属性与调用属性 Link

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

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

var result = (function(n) {
  if (n

严格模式和非严格模式下的参数对象 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

在这个函数里面, 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

加上 严格模式 , 现在改变 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;);

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

总结 Link

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


Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn