1. 程式人生 > >ES6數組擴展

ES6數組擴展

新特性 語義 div 希望 defined 數值 cal ear true

前面的話

  數組是一種基礎的JS對象,隨著時間推進,JS中的其他部分一直在演進,而直到ES5標準才為數組對象引入一些新方法來簡化使用。ES6標準繼續改進數組,添加了很多新功能。本文將詳細介紹ES6數組擴展

創建數組

  在ES6以前,創建數組的方式主要有兩種,一種是調用Array構造函數,另一種是用數組字面量語法,這兩種方法均需列舉數組中的元素,功能非常受限。如果想將一個類數組對象(具有數值型索引和length屬性的對象)轉換為數組,可選的方法也十分有限,經常需要編寫額外的代碼。為了進一步簡化JS數組的創建過程,ES6新增了Array.of()和Array.from()兩個方法

【Array.of()】

  ES6之所以向JS添加新的創建方法,是要幫助開發者們規避通過Array構造函數創建數組時的怪異行為

let items = new Array(2);
console.log(items.length); // 2
console.log(items[0]); // undefined
console.log(items[1]); // undefined
items = new Array("2");
console.log(items.length); // 1
console.log(items[0]); // "2"
items = new Array(1, 2);
console.log(items.length); 
// 2 console.log(items[0]); // 1 console.log(items[1]); // 2 items = new Array(3, "2"); console.log(items.length); // 2 console.log(items[0]); // 3 console.log(items[1]); // "2"

  如果給Array構造函數傳入一個數值型的值,那麽數組的length屬性會被設為該值。如果傳入多個值,此時無論這些值是不是數值型的,都會變為數組的元素。這個特性令人感到困惑,不可能總是註意傳入數據的類型,所以存在一定的風險

  ES6通過引入Array.of()方法來解決這個問題。Array.of()與Array構造函數的工作機制類似,只是不存在單一數值型參數值的特例,無論有多少參數,無論參數是什麽類型的,Array.of()方法總會創建一個包含所有參數的數組

let items = Array.of(1, 2);
console.log(items.length); // 2
console.log(items[0]); // 1
console.log(items[1]); // 2
items = Array.of(2);
console.log(items.length); // 1
console.log(items[0]); // 2
items = Array.of("2");
console.log(items.length); // 1
console.log(items[0]); // "2"

  要用Array.of()方法創建數組,只需傳入希望在數組中包含的值。第一個示例創建了一個包含兩個數字的數組;第二個數組包含一個數宇;最後一個數組包含一個字符串。這與數組字面量的使用方法很相似,在大多數時候,可以用數組字面量來創建原生數組,但如果需要給一個函數傳入Array的構造函數,則可能更希望傳入Array.of()來確保行為一致

function createArray(arrayCreator, value) {
    return arrayCreator(value);
}
let items = createArray(Array.of, value);

  在這段代碼中心createArray()函數接受兩個參數,一個是數組創造者函數,另一個是要插入數組的值。可以傳入Array.of()作為createArray()方法的第一個參數來創建新數組,如果不能保證傳入的值一定不是數字,那麽直接傳入Array會非常危險

  [註意]Array.of()方法不通過Symbol.species屬性確定返回值的類型,它使用當前構造函數(也就是of()方法中的this值)來確定正確的返回數據的類型

【Array.from()】

  JS不支持直接將非數組對象轉換為真實數組,arguments就是一種類數組對象,如果要把它當作數組使用則必須先轉換該對象的類型。在ES5中,可能需要編寫如下函數來把類數組對象轉換為數組

function makeArray(arrayLike) {
    var result = [];
    for (var i = 0, len = arrayLike.length; i < len; i++) {
        result.push(arrayLike[i]);
    }
    return result;
}
function doSomething() {
    var args = makeArray(arguments);
    // 使用 args
}

  這種方法先是手動創建一個result數組,再將arguments對象裏的每一個元素復制到新數組中。盡管這種方法有效,但需要編寫很多代碼才能完成如此簡單的操作。最終,開發者們發現了一種只需編寫極少代碼的新方法,調用數組原生的slice()方法可以將非數組對象轉換為數組

function makeArray(arrayLike) {
    return Array.prototype.slice.call(arrayLike);
}
function doSomething() {
    var args = makeArray(arguments);
    // 使用 args
}

  這段代碼的功能等價於之前的示例,將slice()方法執行時的this值設置為類數組對象,而slice()對象只需數值型索引和length屬性就能夠正確運行,所以任何類數組對象都能被轉換為數組

  盡管這項技術不需要編寫很多代碼,但是我們調用Array.prototype.slice.call(arrayLike)時不能直覺地想到這是在將arrayLike轉換成一個數組。所幸,ES6添加了一個語義清晰、語法簡潔的新方法Array.from()來將對象轉化為數組

  Array.from()方法可以接受可叠代對象或類數組對象作為第一個參數,最終返回一個數組

function doSomething() {
    var args = Array.from(arguments);
    // 使用 args
}

  Array.from()方法調用會基於arguments對象中的元素創建一個新數組,args是Array的一個實例,包含arguments對象中同位置的相同值

  [註意]Array.from()方法也是通過this來確定返回數組的類型的

映射轉換

  如果想要進一步轉化數組,可以提供一個映射函數作為Array.from()的第二個參數,這個函數用來將類數組對象中的每一個值轉換成其他形式,最後將這些結果儲存在結果數組的相應索引中

function translate() {
    return Array.from(arguments, (value) => value + 1);
}
let numbers = translate(1, 2, 3);
console.log(numbers); // 2,3,4

  在這段代碼中,為Array.from()方法傳入映射函數(value)=>value+1,數組中的每個元素在儲存前都會被加1。如果用映射函數處理對象,也可以給Array.from()方法傳入第三個參數來表示映射函數的this值

let helper = {
    diff: 1,
    add(value) {
        return value + this.diff;
    }
};
function translate() {
    return Array.from(arguments, helper.add, helper);
}
let numbers = translate(1, 2, 3);
console.log(numbers); // 2,3,4

  此示例傳入helper.add()作為轉換用的映射函數,由於該方法使用了this.diff屬性,因此需要為Array.from()方法提供第三個參數來指定this的值,從而無須通過調用bind()方法或其他方式來指定this的值了

用Array.from()轉換可叠代對象

  Array.from()方法可以處理類數組對象和可叠代對象,也就是說該方法能夠將所有含有Symbol.iterator屬性的對象轉換為數組

let numbers = {
    *[Symbol.iterator]() {
        yield 1;
        yield 2;
        yield 3;
    }
};
let numbers2 = Array.from(numbers, (value) => value + 1);
console.log(numbers2); // 2,3,4

  由於numbers是一個可叠代對象,因此可以直接將它傳入Array.from()來轉換成數組。此處的映射函數將每一個數字加1,所以結果數組最終包含的值為2、3和4

  [註意]如果一個對象既是類數組又是可叠代的,那麽Array.from()方法會根據叠代器來決定轉換哪個值

數組方法

  ES6延續了ES5的一貫風格,也為數組添加了幾個新的方法。find()方法和findIndex()方法可以協助開發者在數組中查找任意值;fill()方法和copyWithin()方法的靈感則來自於定型數組的使用過程,定型數組也是ES6中的新特性,是一種只包含數字的數組

【find()和findIndex()】

  由於沒有內建的數組搜索方法,因此ES5正式添加了indexOf()和lastIndexOf()兩個方法,可以用它們在數組中查找特定的值。雖然這是一個巨大的進步,但這兩種方法仍有局限之處,即每次只能查找一個值,如果想在系列數字中查找第一個偶數,則必須自己編寫代碼來實現。於是ES6引入了find()方法和findIndex()方法來解決這個問題

  find()方法和findIndex()方法都接受兩個參數:一個是回調函數;另一個是可選參數,用於指定回調函數中this的值。執行回調函數時,傳入的參數分別為數組中的某個元素、該元素在數組中的索引和數組本身,與傳入map()和forEach()方法的參數相同。如果給定的值滿足定義的標準,回調函數應返回true。一旦回調函數返回true,find()方法和findIndex()方法都會立即停止搜索數組剩余的部分

  二者間唯一的區別是,find()方法返回查找到的值,findIndex()方法返回查找到的值的索引

let numbers = [25, 30, 35, 40, 45];
console.log(numbers.find(n => n > 33)); // 35
console.log(numbers.findIndex(n => n > 33)); // 2

  這段代碼通過調用find()方法和findIndex()方法來定位numbers數組中第一個比33大的值,調用find()方法返回的是35,而調用findIndex()方法返回的是35在numbeps數組中的位置2

  如果要在數組中根據某個條件查找匹配的元素,那麽find()方法和findIndex()方法可以很好地完成任務;如果只想查找與某個值匹配的元素,則indexOf()方法和lastIndexOf()方法是更好的選擇

【fill()】

  fill()方法可以用指定的值填充一至多個數組元素。當傳入一個值時,fill()方法會用這個值重寫數組中的所有值

let numbers = [1, 2, 3, 4];
numbers.fill(1);
console.log(numbers.toString()); // 1,1,1,1

  在此示例中,調用numbers.fill(1)方法後numbers中所有的值會變成1,如果只想改變數組某一部分的值,可以傳入開始索引和不包含結束索引(不包含結束索引當前值)這兩個可選參數

let numbers = [1, 2, 3, 4];
numbers.fill(1, 2);
console.log(numbers.toString()); // 1,2,1,1
numbers.fill(0, 1, 3);
console.log(numbers.toString()); // 1,0,0,1

  在numbers.fill(1,2)調用中,參數2表示從索引2開始填充元素,由於未傳入第三個參數作為不包含結束索引,因此使用numbers.length作為不包含結束索引,因而numbers數組的最後兩個元素被填充為1。操作numbers.fill(0,1,3)會將數組中位於索引1和2的元素填充為0。調用fill()時若傳入第二個和第三個參數則可以只填充數組中的部分元素

  [註意]如果開始索引或結束索引為負值,那麽這些值會與數組的length屬性相加來作為最終位置。例如,如果開始位置為-1,那麽索引的值實際為array.length-1,array為調用fill()方法的數組

【copyWithin()】

  copyWithin()方法與fill()方法相似,其也可以同時改變數組中的多個元素。fill()方法是將數組元素賦值為一個指定的值,而copyWithin()方法則是從數組中復制元素的值。調用copyWithin()方法時需要傳入兩個參數:一個是該方法開始填充值的索引位置,另一個是開始復制值的索引位置

  比如復制數組前兩個元素的值到後兩個元素

let numbers = [1, 2, 3, 4];
// 從索引 2 的位置開始粘貼
// 從數組索引 0 的位置開始復制數據
numbers.copyWithin(2, 0);
console.log(numbers.toString()); // 1,2,1,2

  這段代碼從numbers的索引2開始粘貼值,所以索引2和3將被重寫。給CopyWithin()傳入第二個參數0表示,從索引0開始復制值並持續到沒有更多可復制的值

  默認情況下,copyWithin()會一直復制直到數組末尾的值,但是可以提供可選的第三個參數來限制被重寫元素的數量。第三個參數是不包含結束索引,用於指定停止復制值的位置

let numbers = [1, 2, 3, 4];
// 從索引 2 的位置開始粘貼
// 從數組索引 0 的位置開始復制數據
// 在遇到索引 1 時停止復制
numbers.copyWithin(2, 0, 1);
console.log(numbers.toString()); // 1,2,1,4

  在這個示例中,由於可選的結束索引被設置為了1,因此只有位於索引0的值被復制了,數組中的最後一個元素保持不變

  [註意]正如fill()方法一樣,copyWithin()方法的所有參數都接受負數值,並且會自動與數組長度相加來作為最終使用的索引

  fill()和copyWithin()這兩個方法起源於定型數組,為了保持數組方法的一致性才添加到常規數組中的。如果使用定型數組來操作數字的比特,這些方法將大顯身手

ES6數組擴展