如何使用 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 國際