如何使用ES6中的參數

ECMAScript 6(或者叫 ECMAScript 2015)是 ECMAScript 的最新標準,極大的提升了 JavaScript 中處理參數的能力。如今咱們可使用 rest 參數(rest parameters)、默認值(default values)和解構(destructuring)以及其餘許多新的特性。本文咱們將探索參數(arguments)和參數(parameter)的方方面面,看一下ES6是如何對他們改進和提高的。前端

Arguments 和 Parameters

arguments 和 Parameters 的含義一般是能夠互換的。儘管如此,爲了本文的目標,仍是要作出區分。在大多數的標準中,函數聲明時給出的叫作 parameters(或者叫 formal parameters),而傳遞給函數的叫作的 arguments(或者叫 actual arguments),看下面的函數:git

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

在這個函數中,param1param2 是函數的 parameters,而傳遞給函數的值(1020)是 arguments。es6

譯者注:本文後面再也不區分 arguments 和 parameters,統一譯做參數。github

擴展運算符(...)

在 ES5 中,apply() 方法能夠很方便將數組做爲參數傳遞給函數,常常用於使用 Math.max() 來取得數組的最大值。看下面的代碼段:數組

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

Math.max() 方法不支持數組,只接受數字做爲參數。當數組傳遞給函數,函數會拋出錯誤。可是當使用 apply() 方法後,數組變成了一個個單獨的數組傳遞給了函數,因此 Math.max() 就可以正確的執行了。安全

幸運的是,ES6 給咱們帶來了擴展運算符,咱們就沒必要再繼續使用 apply() 方法了。咱們能夠將表達式輕鬆的展開爲多個參數。微信

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

在這裏咱們經過擴展運算符將 myArray 展開成了一個個單獨的值。雖然 ES5 中咱們能夠經過 apply() 方法來模擬擴展運算符,可是語法上讓人迷惑,而且缺乏可擴展性。擴展運算符不只易於使用,還帶來了許多新的特性。好比,你能夠在函數調用時屢次使用擴展運算符,而且還能夠和其餘參數混合在一塊兒。app

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

擴展運算符另外一大好處就是他能夠很容易的和構造函數(constructor)一塊兒使用:ecmascript

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

當前咱們可使用 ES5 來重寫上面的代碼,不過咱們須要一個複雜的方法來避免一個類型錯誤:函數

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)

REST 參數

rest 參數和擴展運算符是同樣的語法,可是他不是將數組展開成一個個的參數,而是將一個個參數轉換爲數組。

譯者注:rest 參數和擴展運算符雖然同樣的語法,在這裏你就能夠看出做者強調的 arguments 和 parameters 的區別了。擴展運算符用於函數調用的參數(arguments)中,而 rest 參數用於函數聲明的參數(parameters)中。

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

若是沒有提供參數,rest 參數會被設置爲空數組:

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

當建立可見函數(接受數量可變的參數的函數)的時候,rest 參數就顯得十分有用。由於 rest 參數是一個數組,因此能夠很方便的替換 arguments 對象(將會在下文討論)。看下面一個使用 ES5 編寫的方法:

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

這個函數的做用是檢查一個字符串是否包含指定的一系列字符串。這個函數的第一個問題就是,咱們必須查看函數體才知道函數接受多個參數。另外 arguments 的迭代必須從 1 開始,由於 arguments[0] 是第一個參數。若是咱們稍後給第一參數以後再添加參數,或許咱們就忘記更新這個循環了。使用 rest 參數,咱們能夠很輕易的避開這個問題:

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 中的性能問題。儘管如此,rest 參數並不能無限制使用,舉個例子,它只能是最後一個參數,不然會致使語法錯誤。

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

另外一個限制方法聲明時只容許一個 rest 參數:

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

默認值

ES5 中的默認參數

ES5 中 JavaScript 並不支持默認值,但這裏有個很簡單的實現,使用 OR
運算符(||),咱們能夠很容易的模擬默認參數,看下面的代碼:

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 運算符(||)。這個運算符首先檢查第一個值,若是是 truthy,運算符會返回它,不然返回第二個參數。

這種方法在函數內很經常使用,但也存在瑕疵。若是傳遞 0 或者 null 也會返回默認值。由於它們被認爲是 falsy 值。因此若是咱們確實須要給函數傳遞 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

在這個函數中,經過檢查參數的類型是否爲 undefined 來肯定是否要賦予默認值。這種方法代碼量稍微大一些,但更安全,可讓咱們給函數傳遞 0 或者 null

ES6 中的默認參數

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)

須要注意的是,只有缺乏第二個參數的時候,gegParam 方法纔會執行,因此當咱們使用兩個參數 multiply() 的時候並不會彈出 alert。

默認參數另外一個有意思的特性是在方法聲明是能夠引用其餘參數和變量做爲默認參數:

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

解構賦值

解構賦值是 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: 'http',
  port: 800,
  delay: 150,
  retries: 10,
  timeout: 500,
  log: true
};
initiateTransfer(options);

這種模式 JavaScript 開發者常用,而且很好用。但咱們必須進入函數體內才知道到底須要多少參數,使用解構參數賦值,咱們能夠在函數聲明時很清晰的指定須要的參數。

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

在這個例子中,每一個屬性都被賦予默認值,就無需在函數體內手動檢查 undefined 的參數再賦予默認值。

參數傳遞

函數傳參有兩種方式:引用傳遞和值傳遞。若是是引用傳遞,修改參數會引發全局的變化,若是是值傳遞,只會引發函數內的變化。

在一些語言中,像 Visual Basic 和 PowerShell,咱們能夠選擇聲明是值傳遞仍是引用傳遞,但 JavaScript 不是這樣。

值傳遞

嚴格來講,JavaScript只能值傳遞。當咱們經過值傳遞給函數傳參,就在函數做用域內建立了這個值得副本。因此任何值得變化都只會反映在函數內部。看下面的例子:

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

正如你看到的,對象的屬性在函數體內部被修改,可是卻影響到了函數外部的對象。

當咱們傳遞一個非原始的值,像數組或者對象,程序會在內存中建立一個對象,指向原始地址。若是被修改,原始值也會隨之修改。

類型檢查和缺失或多餘參數

在強類型的語言中,咱們必須在函數聲明時聲明參數的類型,但 JavaScript 中沒有這種特性,在 JavaScript 中,並不關心傳遞給函數的參數的類型和個數。

假設咱們有一個函數,僅接受一個參數。當咱們調用這個函數的使用,咱們並不限制到底傳遞給函數多少個參數,甚至能夠選擇不傳,都不會產生錯誤。

參數的個數能夠分爲兩種狀況:

  • 參數缺失

    缺失的變量賦值爲 undefined

  • 參數過多

    多餘的參數會被忽略,但能夠從 arguments 變量中取到(下文即將討論)。

強制參數

函數調用中若是函數缺失,它會被設置爲 undefined。咱們能夠利用這一點,若是參數缺失就拋出錯誤:

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

在 ES6 中,咱們能夠更近一步,使用默認參數來設置強制參數:

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 對象

在 ES4 的時候默認參數就被加入,來代替 arguments 對象,但 ES4 並無實現。隨着 ES6 的發佈,JavaScript 如今官方支持了默認參數。但並無取消支持 arguments 的計劃。

arguments 對象是一個類數組的對象,能夠在全部的函數中取到。arguments 經過數字索引來獲取傳入的參數,而不是經過參數的名字。這個對象容許咱們給函數傳入任意多的參數。看下面的代碼判斷:

function checkParams(param1) {
    console.log(param1);    // 2
    console.log(arguments[0], arguments[1]);    // 2 3
    console.log(param1 + arguments[0]);    // 4
}
checkParams(2, 3);

這個函數指望傳入一個參數,當咱們傳入兩個參數調用它的時候,咱們經過 param1 或者 arguments[0] 來獲取第一個參數,但第二個參數只能經過 arguments[1] 獲取。也便是說,arguments 對象能夠和有命名的參數一塊兒使用。

arguments 對象包含了全部傳入函數的參數,而且索引的起始是 1。當咱們但願獲取更多的參數的時候,咱們會使用 arguments[2]arguments[3] 等等。

咱們能夠跳過全部的參數命名設置,僅僅使用 arguments 對象:

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);

arguments 對象是一個類數組對象,可是缺乏像 sliceforeach 等方法。爲了在 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 方法進行排序。

ES6 有一種更直接的方法,Array.from(),ES6 新增的方法,用來經過類數組對象建立一個新的數組。

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

length 屬性

雖然 arguments 對象並非嚴格意義的數組,但它有一個 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
    }
}

rest 參數是數組,因此他也有 length 屬性,咱們用 ES6 來重寫上面的方法:

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 指向調用當前正在運行函數的函數。在 ES5 嚴格模式下,這些屬性是被廢棄掉的,若是要訪問它們會拋出錯誤。

arguments.callee 屬性在遞歸函數(遞歸函數是一個普通函數,經過它的簽名指向自身)下頗有用,尤爲是函數的簽名不可用時(也就是匿名函數)。由於匿名函數沒有名字,惟一指向自身的方法就是經過 arguments.callee

var result = (function(n) {
  if (n <= 1) {
    return 1;
  } else {
    return n * arguments.callee(n - 1);
  }
})(4);   // 24

嚴格模式和非嚴格模式下的 arguments

在 ES5 非嚴格模式下, 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 的改變。事實上,他們是同個變量的不一樣名稱。在 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 的嚴格模式是一致的。可是請記住,在函數聲明時使用了默認參數,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 對象。

結論

ES6 給 JavaScript 帶來了許多大大小小的改進。愈來愈多的開發者開始使用 ES6,並且不少全部的特性均可以無障礙使用。本文咱們學習了 ES6 是如何提高JavaScript 處理參數的能力的。但咱們僅僅學了 ES6 的一點皮毛。更多的有趣的特性等着咱們去挖掘!


看下時間如今正好是23:23,幾乎用了一個下午和晚上把這篇文章讀完又翻譯完,這篇文章結合 ES5 和 ES6 來說解,收益頗多。不過翻譯水平有限,求多提意見多多指教 ~

原文地址: How To Use Arguments And Parameters In ECMAScript 6

小廣告

歡迎關注咱們的微信公衆號:
小前端FE(smallsfe)

相關文章
相關標籤/搜索