ES6躬行記(2)——擴充套件運算子和剩餘引數
擴充套件運算子(Spread Operator)和剩餘引數(Rest Parameter)的寫法相同,都是在變數或字面量之前加三個點(...),並且只能用於包含Symbol.iterator屬性的可迭代物件(iterable)。雖然兩者之間有諸多類似,但它們的功能和應用場景卻完全不同。擴充套件運算子能把整體展開成個體,常用於函式呼叫、陣列或字串處理等;而剩餘引數正好相反,把個體合併成整體,常用於函式宣告、解構引數等。此處的整體可能是陣列、字串或類陣列物件等,個體可能是字元、陣列的元素或函式的引數等。
一、擴充套件運算子
擴充套件運算子的用途簡單概括,可以分為以下三種。
(1)替代函式的apply()方法。
(2)簡化函式呼叫時傳遞實參的方式。
(3)處理陣列和字串。
1)apply()
函式的apply()方法能夠間接呼叫其它物件的方法,往往能收穫奇效,例如用Math物件的min()方法獲取陣列中的最小值,min()方法本來只接收一組引數,利用apply()方法後就能直接傳遞一個數組,如下所示。
let arr = [1, 0, 2], min; min = Math.min(1, 0, 2); //一組引數的呼叫方式 min = Math.min.apply(undefined, arr); //利用apply()間接呼叫
雖然apply()方法很便捷,但每次都必須設定this的指向(即定義第一個引數),並且迂迴的寫法可能會為理解程式碼意圖設定障礙。而使用擴充套件運算子後,既能以簡單的語法形式完成相同的功能,還能更清晰的表明程式碼的意圖。下面用擴充套件運算子查詢陣列中的最小值。
min = Math.min(...arr); console.log(min); //0
2)傳參
函式在被呼叫時,實參通常都是以逗號分隔的序列形式傳遞到函式體內。如果實參的值被儲存在陣列中,那麼就要一個一個的讀取陣列中指定位置的元素,例如建立一個日期物件(呼叫它的建構函式),把年月日的資訊儲存在陣列中,如下程式碼所示。註釋中的日期並不是預設的顯示格式,只是為了更容易閱讀而這麼寫的。
let date = [2018, 6, 9]; new Date(date[0], date[1], date[2]); //2018-7-6
換成擴充套件運算子的寫法後,實參的傳遞就變得非常的簡潔,如下所示。
new Date(...date); //2018-7-6
不僅如此,在呼叫函式的時候,還可以使用多個擴充套件運算子,並能和普通的實參混合使用,如下所示。
let time = [10, 28]; new Date(...date, ...time, 45); //2018-7-6 10:28:45
3)陣列和字串
在擴充套件運算子出現之前,要執行陣列的複製、合併等操作,需要呼叫陣列的slice()、concat()、unshift()等方法。這些方法到底是單獨呼叫還是組合呼叫,由實際情況而定。下面是一個數組複製與合併的簡單示例。
let arr1 = [1, 2, 3], arr2, arr3; arr2 = arr1.slice(); //複製陣列 arr3 = arr2.concat(arr1); //合併陣列 console.log(arr1); //[1, 2, 3] console.log(arr2); //[1, 2, 3] console.log(arr3); //[1, 2, 3, 1, 2, 3]
接下來用擴充套件運算子來完成同樣的功能,如下程式碼所示。
arr2 = [...arr1]; //複製陣列 arr3 = [...arr1, ...arr2]; //合併陣列
在實際專案中,肯定會碰到各式各樣的陣列操作,合理利用擴充套件運算子,不但可以節省大量的程式碼,還能提升程式碼的可讀性。
擴充套件運算子不僅能處理陣列,還能處理字串。在JavaScript中,字串的行為類似於陣列,但它不能直接呼叫陣列的方法,需要先執行自己的split()方法轉換成陣列。而使用擴充套件運算子後,就能省去這步操作,具體如下所示,注意,包裹的方括號不能省略。
let str = "strick"; str.split(""); //["s", "t", "r", "i", "c", "k"] [...str]; //["s", "t", "r", "i", "c", "k"]
二、剩餘引數
在JavaScript的函式中,宣告時定義的形參個數可以和傳入的實參個數不同。當實參個數大於形參個數時,ES6新增的剩餘引數能把沒有對應形參的實參收集到一個數組中。下面是一個簡單的示例。
function func(name, ...args) { console.log(name); console.log(args[0]); } func("strick"); //首先輸出"strick",然後輸出undefined func("freedom", 29); //首先輸出"freedom",然後輸出29
第一次呼叫func()函式只傳入了一個實參,對應的形參就是name。第二次呼叫func()函式傳入了兩個實參,第一個有對應的形參,而第二個並沒有對應的形參。此時,該實參就會被放到陣列args(就是剩餘引數)中,變為該陣列的一個元素,在函式體內就能通過陣列的索引讀取該實參。有一點要注意,剩餘引數不會影響函式的length屬性,該屬性的值表示形參個數。以上面的func()函式為例,... args並不是一個形參,因此,func()函式的length屬性值為1。
console.log(func.length); //1
1)解構
剩餘引數可以被解構(將在第3篇中講解),這意味著剩餘引數中的元素可以被賦給函式體中的同名變數,如下所示。
function destructuring (name, ...[age]) { console.log(name); console.log(age); } destructuring ("jane", 28); //首先輸出"jane",然後輸出28
引入剩餘引數就是為了能替代函式內部的arguments,它是一個類陣列物件,管理著實參列表,該列表包含了傳入到函式內的所有實參。由於arguments物件不具備陣列的方法,所以很多時候在使用之前要先轉換成一個數組。而剩餘引數本來就是一個數組,避免了這多餘的一步,使用起來既優雅又自然。
2)兩點限制
剩餘引數有兩點限制,在使用時需要引起注意。第一點是在函式中宣告時必須放在最後,下面是一種錯誤的寫法。
function restrict1(...args, name) { //丟擲語法錯誤 }
第二點是不能在物件字面量的setter方法中宣告,因為該方法只接收一個引數,而剩餘引數不會限制引數的數量。注意,setter方法在定義時會用set替代function關鍵字,下面是一個會丟擲語法錯誤的例子。
var obj = { set age(...value) { this._age = value; } };