深入理解ES6--10.增強的陣列功能

你聽___發表於2018-05-07

深入理解ES6--10.增強的陣列功能

主要知識點:建立陣列、陣列上的新方法、型別化陣列

增強的陣列功能的知識點

1. 建立陣列

在 ES6 之前建立陣列主要存在兩種方式: Array 構造器與陣列字面量寫法。這兩種方式都需要將陣列的項分別列出,並且還要受到其他限制。將“類陣列物件”(即:擁有數值型別索引與長度屬性的物件) 轉換為陣列也並不自由,經常需要書寫額外的程式碼。為了使陣列更易建立,ES6 新增了Array.of()Array.from() 方法。

Array.of()方法

Array.of()方法會將方法的傳入引數全部作為陣列裡的資料內容,而不管引數的數量與型別:

let items = Array.of(1,'2');
console.log(items); //Array(2)
console.log(items.length); //2
複製程式碼

Array.from()方法

可使用Array.from()方法可以將類陣列物件和可迭代物件轉換成陣列。該方法接收三個引數:

  1. 待轉換的可迭代物件或者類陣列物件(具有數值索引和長度屬性的物件);
  2. 可選的對映函式;
  3. 指定對映函式內部的 this 值;

對映轉換

如果你想實行進一步的陣列轉換,你可以向 Array.from() 方法傳遞一個對映用的函式作為第二個引數。此函式會將類陣列物件的每一個值轉換為目標形式,並將其儲存在目標陣列的對應位置上。例如:

function trans(values){
	return Array.from(arguments,item=>item+1);
}

console.log(trans(1,2,3)); //[2, 3, 4]
複製程式碼

可迭代物件上使用

Array.from() 方法不僅可用於類陣列物件,也可用於可迭代物件,也就是說可以將任意包含 Symbol.iterator 屬性的物件轉換成陣列,例如:

let obj = {
	*[Symbol.iterator](){
		yield 1;
		yield 2;
		yield 3;
	}
}

console.log(Array.from(obj)); //[1, 2, 3]
複製程式碼

2. 陣列上所有的新方法

2.1 find()和findIndex()方法

indexOf()lastIndexOf()方法用於查詢特定值在陣列中的位置,而如果需要查詢在陣列中滿足特定條件的元素就需要使用find()findIndex()方法。

find() 方法和 findIndex() 方法均接受兩個引數:一個是回撥函式、一個是可選的指定回撥函式中的 this 值。回撥函式中有三個引數:

  1. 陣列中的資料項元素;
  2. 元素在陣列中的索引位置;
  3. 陣列例項物件本身;

回撥函式與 map()forEach() 等方法的回撥函式中的引數一致。例如,需要找到在陣列中大於5的元素:

let arr = Array.of(1,5,8,9);

console.log(arr.find(item=>item>5)); //8
console.log(arr.findIndex(item=>item>5)); //2
複製程式碼

可以看出,find()方法返回的是滿足特定條件的資料項,而 findIndex() 方法返回的是滿足特定條件的元素索引。

2.2 fill()方法

fill()方法能使用特定值填充陣列中的一個或多個元素。當只使用一個引數的時候,該方法會用該引數的值填充整個陣列。若你不想改變陣列中的所有元素,而只想改變其中一部分,那麼可以使用可選的起始位置引數與結束位置引數(不包括結束位置的那個元素)

let arr = Array.of(1,5,8,9);

console.log(arr.fill(1,2,4)); //[1, 5, 1, 1]
複製程式碼

若只給定起始位置,不指定結束位置的話,預設結束位置為陣列末尾。

2.3 copyWithin()方法

copyWithin() 方法與 fill() 類似,可以一次性修改陣列的多個元素。不過,與 fill() 使用單個值來填充陣列不同, copyWithin() 方法允許你在陣列內部複製自身元素。為此你需要傳遞兩個引數給 copyWithin() 方法:從什麼位置開始進行填充,以及被用來複制的資料的起始位置索引,以及可選的複製結束的位置(不包含該位置)

//copyWithin()
let arr = Array.of(1,5,8,9);
console.log(arr.copyWithin(2,0,2)); //[1, 5, 1, 5]
複製程式碼

3. 型別化陣列

ES6 採納了型別化陣列,將其作為語言的一個正式部分,以確保在 JS 引擎之間有更好的相容性,並確保與 JS 陣列有更好的互操作性。

3.1 數值資料型別

JS 數值使用 IEEE 754 標準格式儲存,使用 64 位來儲存一個數值的浮點數表示形式,該格式在 JS 中被同時用來表示整數與浮點數;當值改變時,可能會頻繁發生整數與浮點數之間的格式轉換。而型別化陣列則允許儲存並操作八種不同的數值型別:

  1. 8 位有符號整數(int8)
  2. 8 位無符號整數(uint8)
  3. 16 位有符號整數(int16)
  4. 16 位無符號整數(uint16)
  5. 32 位有符號整數(int32)
  6. 32 位無符號整數(uint32)
  7. 32 位浮點數(float32)
  8. 64 位浮點數(float64)

所有與型別化陣列相關的操作和物件都圍繞著這八種資料型別。為了使用它們,要先建立一個陣列緩衝區用於儲存資料。

3.2 陣列緩衝區

陣列緩衝區(array buffer) 是記憶體中包含一定數量位元組的區域,而所有的型別化陣列都基於陣列緩衝區。建立陣列緩衝區使用 ArrayBuffer 構造器:

let buffer = new ArrayBuffer(10);
console.log(buffer.byteLength); //10
複製程式碼

呼叫 ArrayBuffer 構造器時,只需要傳入單個數值用於指定緩衝區包含的位元組數,而本例就建立了一個 10 位元組的緩衝區。當陣列緩衝區被建立完畢後,你就可以通過檢查 byteLength屬性來獲取緩衝區的位元組數。

還可以使用slice()方法來建立一個包含已有緩衝區部分內容的陣列緩衝區,其中slice()方法可以使用起始位置和結束位置(不包含結束位置):

let buffer = new ArrayBuffer(10);
let buf = buffer.slice(4,8);
console.log(buf.byteLength); //4
複製程式碼

僅僅建立一個陣列緩衝器不能寫入資料,是沒有任何意義的。要想寫入資料,需要建立檢視(view)。

3.3 使用檢視運算元組緩衝區

陣列緩衝區代表了一塊記憶體區域,而檢視(views) 則是你操作這塊區域的介面。檢視工作在陣列緩衝區或其子集上,可以讀寫某種數值資料型別的資料。 DataView 型別是陣列緩衝區的通用檢視,允許你對前述所有八種數值資料型別進行操作。

建立檢視,需要使用 DataView() 構造器,可以指定可選引數-位元組偏移量以及所要包含的位元組數。如果不指定所要包含的位元組數,則預設為從位元組偏移量直到陣列緩衝區的末尾。

let buffer = new ArrayBuffer(10),
view = new DataView(buffer, 5, 2);
複製程式碼

此例中的 view 只能使用索引值為 5 與 6 的位元組。使用這種方式,你可以在同一個陣列緩衝區上建立多個不同的檢視,這樣有助於將單塊記憶體區域供給整個應用使用,而不必每次在有需要時才動態分配記憶體。

獲取檢視資訊

可以通過檢視的可讀屬性來獲取檢視的資訊:

  • buffer :該檢視所繫結的陣列緩衝區;
  • byteOffset :傳給 DataView 構造器的第二個引數,如果當時提供了的話(預設值為0);
  • byteLength :傳給 DataView 構造器的第三個引數,如果當時提供了的話(預設值為該緩衝區的 byteLength 屬性。

讀取或寫入資料

對應於 JS 所有八種數值資料型別, DataView 檢視的原型分別提供了在陣列緩衝區上寫入資料的一個方法、以及讀取資料的一個方法。所有方法名都以“set”或“get”開始,其後跟隨著對應資料型別的縮寫。下面列出了能夠操作 int8uint8 型別的讀取/寫入方法:

  • getInt8(byteOffset, littleEndian) :從 byteOffset 處開始讀取一個 int8 值;
  • setInt8(byteOffset, value, littleEndian) :從 byteOffset 處開始寫入一個 int8 值;
  • getUint8(byteOffset, littleEndian) :從 byteOffset 處開始讀取一個無符號 int8 值;
  • setUint8(byteOffset, value, littleEndian) :從 byteOffset 處開始寫入一個無符號int8 值。

get”方法接受兩個引數:開始進行讀取的位元組偏移量、以及一個可選的布林值,後者用於指定讀取的值是否採用低位元組優先方式(注:預設值為 false ) 。“set”方法則接受三個引數:開始進行寫入的位元組偏移量、需要寫入的資料值、以及一個可選的布林值用於指定是否採用低位元組優先方式儲存資料。

如果針對的是16位或者32位整數的話,只需要將上面的方法中的8,相應的改變成16或者32,就可以操作16位或者32值。

操作浮點數,提供了下面這些方法:

  • getFloat32(byteOffset, littleEndian) :從 byteOffset 處開始讀取一個 32 位的浮點數;
  • setFloat32(byteOffset, value, littleEndian) :從 byteOffset 處開始寫入一個 32 位的浮點數;
  • getFloat64(byteOffset, littleEndian) :從 byteOffset 處開始讀取一個 64 位的浮點數;
  • setFloat64(byteOffset, value, littleEndian) :從 byteOffset 處開始寫入一個 64 位的浮點數;

例如使用上述這些檢視上的方法來進行操作:

let buffer = new ArrayBuffer(2),
view = new DataView(buffer);

view.setInt8(0,1);
console.log(view.getInt8(0)); //1
複製程式碼

3.4 型別化陣列就是檢視

ES6 的型別化陣列實際上也是針對陣列緩衝區的特定型別檢視,你可以使用這些陣列物件來處理特定的資料型別,而不必使用通用的 DataView 物件。一共存在八種特定型別檢視,對應著八種數值資料型別。型別化陣列的構造器有:Int8Array、Uint8Array、Uint8ClampedArray、Int16Array、Uint16Array、Int32Array、Uint32Array、Float32Array、Float64Array。

建立特定型別檢視

建立特定型別檢視有三種方式:

  1. 第一種方式是使用與建立 DataView 時相同的引數,即:一個陣列緩衝區、一個可選的位元組偏移量、以及一個可選的位元組數量;

  2. 第二種方式是傳遞單個數值給型別化陣列的構造器,此數值表示該陣列包含的元素數量(而不是分配的位元組數) 。構造器將會建立一個新的緩衝區,分配正確的位元組數以便容納指定數量的陣列元素,而你也可以使用 length 屬性來獲取這個元素數量;

  3. 第三種方式是向構造器傳遞單個物件引數,可以是下列四種物件之一:

    1. 型別化陣列:陣列所有元素都會被複制到新的型別化陣列中。例如,如果你傳遞一個 int8型別的陣列給 Int16Array 構造器,這些 int8 的值會被複制到 int16 陣列中。新的型別化陣列與原先的型別化陣列會使用不同的陣列緩衝區。
    2. 可迭代物件:該物件的迭代器會被呼叫以便將資料插入到型別化陣列中。如果其中包含了不匹配檢視型別的值,那麼構造器就會丟擲錯誤。
    3. 陣列:該陣列的元素會被插入到新的型別化陣列中。如果其中包含了不匹配檢視型別的值,那麼構造器就會丟擲錯誤。
    4. 類陣列物件:與傳入陣列的表現一致。

    //第一種方式 let buffer = new ArrayBuffer(10), view1 = new Int8Array(buffer), view2 = new Int8Array(buffer, 5, 2);

    //第二種方式 let ints = new Int16Array(2), floats = new Float32Array(5);

    //第三種方式 let ints1 = new Int16Array([25, 50]), ints2 = new Int32Array(ints1);

3.5 型別化陣列與常規陣列的相似點

型別化陣列在很多場景中都可以像常規陣列那樣被使用。例如,你可以使用 length 屬性來獲取型別化陣列包含的元素數量,還可以使用數值型別的索引值來直接訪問型別化陣列的元素。它們二者具有的相似點:

  1. 公共方法

型別化陣列擁有大量與常規陣列等效的方法:

  • copyWithin()
  • entries()
  • fill()
  • filter()
  • find()
  • findIndex()
  • forEach()
  • indexOf()
  • join()
  • keys()
  • lastIndexOf()
  • map()
  • reduce()
  • reduceRight()
  • reverse()
  • slice()
  • some()
  • sort()
  • values()

相同的迭代器

與常規陣列相同,型別化陣列也擁有三個迭代器,它們是 entries() 方法、 keys()方法與values() 方法。這就意味著你可以對型別化陣列使用擴充套件運算子,或者對其使用 for-of 迴圈,就像對待常規陣列。

of()和from()方法

所有的型別化陣列都包含靜態的of()from() 方法,作用類似於 Array.of()Array.from() 方法。其中的區別是型別化陣列的版本會返回型別化陣列,而不返回常規陣列。

3.5 型別化陣列與常規陣列的差異

二者最重要的區別就是型別化陣列並不是常規陣列,型別化陣列並不是從 Array 物件派生的,使用 Array.isArray() 去檢測會返回 false

行為差異

常規陣列可以被伸展或是收縮,然而型別化陣列卻會始終保持自身大小不變。你可以對常規陣列一個不存在的索引位置進行賦值,但在型別化陣列上這麼做則會被忽略。

let ints = new Int16Array([25, 50]);
console.log(ints.length); // 2
console.log(ints[0]); // 25
console.log(ints[1]); // 50
ints[2] = 5;
console.log(ints.length); // 2
console.log(ints[2]); // undefined
複製程式碼

在本例中,儘管對索引值 2 的位置進行了賦值為 5 的操作,但 ints 陣列卻完全沒有被伸展,陣列的長度屬性保持不變,所賦的值也被丟棄了。

型別化陣列也會對資料型別進行檢查以保證只使用有效的值,當無效的值被傳入時,將會被替換為 0

let ints = new Int16Array(["hi"]);
console.log(ints.length); // 1
console.log(ints[0]); // 0
複製程式碼

這段程式碼試圖用字串值 "hi" 建立一個 Int16Array ,而字串對於型別化陣列來說當然是無效的值,因此該字串被替換為 0 並插入陣列。此陣列的長度僅僅是 1 ,而 ints[0]只包含了 0 這個值。

所有在型別化陣列上修改專案值的方法都會受到相同的限制,例如當 map() 方法使用的對映函式返回一個無效值的時候,型別化陣列會使用 0 來代替返回值

let ints = new Int16Array([25, 50]),
mapped = ints.map(v => "hi");
console.log(mapped.length); // 2
console.log(mapped[0]); // 0
console.log(mapped[1]); // 0
console.log(mapped instanceof Int16Array); // true
console.log(mapped instanceof Array); // false
複製程式碼

由於字串值 "hi" 並不是一個 16 位整數,它在結果陣列中就被替換成為 0 。

遺漏的方法

儘管型別化陣列擁有常規陣列的很多同名方法,但仍然缺少了幾個陣列方法,包括下列這些:

  • concat()
  • pop()
  • push()
  • shift()
  • splice()
  • unshift()

附加的方法

型別化陣列還有兩個常規陣列所不具備的方法: set() 方法與 subarray() 方法。這兩個方法作用相反: set() 方法從另一個陣列中複製元素到當前的型別化陣列,而 subarray() 方法則是將當前型別化陣列的一部分提取為新的型別化陣列。

**set() 方法接受一個陣列引數(無論是型別化的還是常規的) 、以及一個可選的偏移量引數,後者指示了從什麼位置開始插入資料(預設值為 0 ) **。陣列引數中的資料會被複制到目標型別化陣列中,並會確保資料值有效。

let ints = new Int16Array(4);
ints.set([25, 50]);
ints.set([75, 100], 2);
console.log(ints.toString()); // 25,50,75,100
複製程式碼

這段程式碼建立了一個包含四個元素的 Int16Array ;第一次呼叫 set() 複製了兩個值到陣列起始的兩個位置;而第二次呼叫set() 則使用了一個值為 2 的偏移量引數,指明應當從陣列的第三個位置(索引 2 ) 開始放置所複製的資料。

subarray() 方法接受一個可選的開始位置索引引數、以及一個可選的結束位置索引引數(像slice() 方法一樣,結束位置的元素不會被包含在結果中) ,並會返回一個新的型別化陣列。你可以同時省略這兩個引數,從而建立原型別化陣列的一個複製品。

let ints = new Int16Array([25, 50, 75, 100]),
subints1 = ints.subarray(),
subints2 = ints.subarray(2),
subints3 = ints.subarray(1, 3);
console.log(subints1.toString()); // 25,50,75,100
console.log(subints2.toString()); // 75,100
console.log(subints3.toString()); // 50,75
複製程式碼

本例中利用 ints 陣列建立了三個型別化陣列。 subints1 陣列是 ints 的一個複製品,包含了原陣列的所有資訊;而 subints2 則從原陣列的索引 2 位置開始複製,因此包含了原陣列的最末兩個元素(即 75 與 100 ) ;最後的 subints3 陣列值包含了原陣列的中間兩個元素,因為呼叫 subarray() 時同時使用了起始位置與結束位置引數。

4. 總結

  1. ES6 延續了 ES5 的工作以便讓陣列更加有用。新增了兩種建立陣列的方式: Array.of() 方法、以及 Array.from() 方法,其中後者可以將可迭代物件或類陣列物件轉換為正規陣列;
  2. fill() 方法與 copyWithin() 方法允許你替換陣列內的元素。 find() 方法與 findIndex() 方法在陣列中查詢滿足特定條件的元素時會非常有用,其中前者會返回滿足條件的第一個元素,而後者會返回該元素的索引位置;
  3. 型別化陣列並不是嚴格的陣列,它們並沒有繼承 Array 物件,但它們的外觀和行為都與陣列有許多相似點。型別化陣列包含的資料型別是八種數值資料型別之一,基於陣列緩衝區物件建立,用於表示按位儲存的一個數值或一系列數值。型別化陣列能夠明顯提升按位運算的效能,因為它不像 JS 的常規數值型別那樣需要頻繁進行格式轉換。

相關文章