文章出處

前面的話

  數組是一種基礎的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的一貫風格,也為數組添加了幾個新的方法:includes()方法返回一個布爾值,表示數組是否包含給定的值;find()方法和findIndex()方法可以協助開發者在數組中查找任意值;fill()方法和copyWithin()方法的靈感則來自于定型數組的使用過程,定型數組也是ES6中的新特性,是一種只包含數字的數組

【includes()】

  Array.prototype.includes方法返回一個布爾值,表示某個數組是否包含給定的值,與字符串的includes方法類似。ES2016 引入了該方法

[1, 2, 3].includes(2)     // true
[1, 2, 3].includes(4)     // false
[1, 2, NaN].includes(NaN) // true

  該方法的第二個參數表示搜索的起始位置,默認為0。如果第二個參數為負數,則表示倒數的位置,如果這時它大于數組長度(比如第二個參數為-4,但數組長度為3),則會重置為從0開始

[1, 2, 3].includes(3, 3);  // false
[1, 2, 3].includes(3, -1); // true

  沒有該方法之前,我們通常使用數組的indexOf方法,檢查是否包含某個值

if (arr.indexOf(el) !== -1) {
  // ...
}

  indexOf方法有兩個缺點,一是不夠語義化,它的含義是找到參數值的第一個出現位置,所以要去比較是否不等于-1,表達起來不夠直觀。二是,它內部使用嚴格相等運算符(===)進行判斷,這會導致對NaN的誤判

[NaN].indexOf(NaN)// -1

  includes使用的是不一樣的判斷算法,就沒有這個問題

[NaN].includes(NaN)// true

  下面代碼用來檢查當前環境是否支持該方法,如果不支持,部署一個簡易的替代版本

const contains = (() =>
  Array.prototype.includes
    ? (arr, value) => arr.includes(value)
    : (arr, value) => arr.some(el => el === value)
)();
contains(['foo', 'bar'], 'baz'); // => false

  另外,Map 和 Set 數據結構有一個has方法,需要注意與includes區分

  1、Map 結構的has方法,是用來查找鍵名的,比如Map.prototype.has(key)WeakMap.prototype.has(key)Reflect.has(target, propertyKey)

  2、Set 結構的has方法,是用來查找值的,比如Set.prototype.has(value)WeakSet.prototype.has(value)

【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()這兩個方法起源于定型數組,為了保持數組方法的一致性才添加到常規數組中的。如果使用定型數組來操作數字的比特,這些方法將大顯身手

 


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()