Skip to content

Instantly share code, notes, and snippets.

@weihanglo
Last active December 2, 2021 05:47
Show Gist options
  • Select an option

  • Save weihanglo/877cb1d7d3118dc04b1b3e7065e01129 to your computer and use it in GitHub Desktop.

Select an option

Save weihanglo/877cb1d7d3118dc04b1b3e7065e01129 to your computer and use it in GitHub Desktop.

Revisions

  1. weihanglo revised this gist Jan 15, 2018. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion binary-data-manipulations-in-javascript.md
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,4 @@
    # Binary Data Manipulations in JavaScript
    # 使用 JavaScript 處理二進位資料

    ![](https://i.imgur.com/Mx1Rbus.png)

  2. weihanglo revised this gist Sep 25, 2017. 1 changed file with 14 additions and 10 deletions.
    24 changes: 14 additions & 10 deletions binary-data-manipulations-in-javascript.md
    Original file line number Diff line number Diff line change
    @@ -2,12 +2,14 @@

    ![](https://i.imgur.com/Mx1Rbus.png)

    由於高度封裝與抽象, JavaScript 的執行效率比不上 C 的語言。例如 JavaScript 的 Array 下標(subscript)是根據 hash key 而非實體記憶體位址 offset 取值,雖然方便,卻多了效能開銷。當 Canvas、WebGL、WebVR 開始走紅,效能越來越受重視,如何讓 JavaScript 達到如同 C 指標般操作 binary data 變得至關重要。
    由於高度封裝與抽象,JavaScript 的執行效率比不上 C 的語言。例如 JavaScript 的 Array 下標(subscript)是根據 hash key 而非實體記憶體位址 offset 取值,雖然方便,卻多了效能開銷。當 Canvas、WebGL、WebVR 開始走紅,效能越來越受重視,如何讓 JavaScript 達到如同 C 指標般操作 binary data 變得至關重要。

    存在許久但最近才變為 ES6 標準「**Typed Array**」就是解放 JavaScript 操作 binary data 能力的好工具!一起來了解 Typed Array 吧!

    _(撰於 2017-09-03,基於 ECMAScript 6+,Node.js 8.3)_

    <!-- more -->

    ## Buffer v.s View

    ES6 引入的 [Typed Array 家族][mdn-typedarrays],可以分為兩大類:**Buffer****View**
    @@ -106,17 +108,19 @@ TypedArray 並非任何一個型別,也非全域可取得的建構函數,而

    ### Array-like Methods

    而 TypedArray 中的 Array,其實就是我們熟悉的 JavaScript Array,可視為「**在 ArrayBuffer 的資料之上,架一層可存取資料的 Array API**」。你想得到的 method `map``filter``reduce` 幾乎應有盡有,而 `push``shift``unshift``splice` 這類會改變 Array 長度的 **Mutator methods** 沒有實作(就只是 buffer 的 reference,`pop` 後資料還是在那裡啊 XD)。
    而 TypedArray 中的 Array,其實就是我們熟悉的 JavaScript Array,可視為「**在 ArrayBuffer 的資料之上,架一層可存取資料的 Array API**」。你想得到的 method `map``filter``reduce` 幾乎應有盡有,而 `push``shift``unshift``splice` 這類會改變 Array 長度的 **Mutator methods** 沒有實作,畢竟 TypedArray 就只是 buffer 的 reference,`pop` 後原始資料依然存在。

    比較好玩的是,`TypedArray#subarray``TypedArray#slice` 同樣是回傳陣列切片,`slice` 是回傳一個淺拷貝(shallow-copy)的**新 Array**,新 array 的 `buffer` property 指向新切出來的 buffer,`byteOffset` 也是依據新的 buffer,所以會是 **0**

    比較好玩的是 `TypedArray#subarray`,和 `slice` 同樣是回傳陣列切片,`slice` 是回傳一個淺拷貝(shallow-copy))的新 Array,而 `subarray` 則是在同一個 buffer 繼續切片,`TypedArray#buffer` 會取得相同的 buffer,`TypedArray#byteOffset` 也是根據原始 buffer 計算 offset。
    `subarray` 則是在同個 buffer 繼續切片,調用 subarray`buffer` 會取得相同的原始 buffer,`byteOffset` 也是根據原始 buffer 計算 offset。

    ### Consturct a TypedArray

    建構 TypedArray 非常簡單,選擇好 data type 之後,`new` 一個就完成了!

    **直接初始化**

    建立一個 4 * 2 bytes 初始值為 0 的 Uint16Array
    建立一個 4 * 2 bytes 初始值為 0 的 Uint16Array

    ```javascript
    const u16 = new Uint16Array(4)
    @@ -247,11 +251,11 @@ dv.getFloat32(8, true) // 加薪囉!!

    各位有沒有注意到,DataView 的 bytes getter/setter 最後面都多帶了一個 boolean 參數?這個參數是指定使用 Little-endian 讀取資料,預設為 `false` 也就是以 Big-endian 讀取。可控制 endian 是 DataView 蠻重要但也頗惱人的特性,在下一節會介紹 Endianness。

    DataView 另一個重要特性就是不會 [buffer overflow][wiki-buffer-overflow],所謂的 buffer overflow 是當寫入一筆資料到指定 buffer 中,萬一寫入的資料大小超過該 buffer 的 boundary,溢出值就會覆寫下個的 bytebuffer overflow 同時是許多駭客的攻擊手法,有潛在的安全性問題。而透過 DataView setter 賦予一個超過型別最大值的數字,並不會覆蓋臨近記憶體位址的資料,而是內部先檢查邊界,處理 overflow 之後,再寫入該記憶體區間。
    DataView 另一個重要特性就是不會 [buffer overflow][wiki-buffer-overflow],所謂的 buffer overflow 是「**當寫入一筆資料到指定 buffer 中,若寫入的資料大小超過該 buffer 的 boundary,溢出值就會覆寫下個 byte**」。這種不安全的性質,也讓 buffer overflow 成為許多駭客的攻擊手法,有潛在的安全性問題。而透過 DataView setter 賦予一個超過型別最大值的數字,並不會覆蓋臨近記憶體位址的資料,而是內部先檢查邊界,處理 overflow 之後,再寫入該記憶體區間,彌補了 buffer overflow 的漏洞

    ## Precautions

    Typed Arrays 幾乎可以做到如同 C 語言般的記憶體操作,十分強大。不過越是自由的 API,就代表要學習更多知識,注意更多細節,以下是操作 TypedArray 該銘記在心的事情:
    Typed Arrays 幾乎可以做到如同 C 語言般細膩的記憶體操作。不過越是自由的 API,就代表要學習更多知識,注意更多細節,以下是操作 TypedArray 該銘記在心的事情:

    - [Endianness (Byte order)][wiki-endianness]
    - [Data Structure Alignment][wiki-alignment]
    @@ -285,13 +289,13 @@ Little-endian 和 Big-endian 可以視為不同的電腦(CPU)講不同的語

    **Q:那我們要如何得知資料的 byte order?**

    如果資料是自己家內部系統使用,其實溝通好就 OK,用 Mixed-endian 也不會有人管你。但如果是外界得來的任意資料,我們可以透過 [BOM(byte order mark)][wiki-bom]來判斷資料屬於那種 endianness。BOM 是一個 Unicode magic number,通常放置在 text stream 的最前端。不過,並不是每個資料都會加上這個 header,而且有時候我們不需要 BOM 資訊,使用資料前還必須先 [strip bom][npm-strip-bom],說實話挺麻煩的。
    如果資料是自己家內部系統使用,其實溝通好就 OK,用 Mixed-endian 也不會有人管你。但如果是外界得來的任意資料,我們可以透過[BOM(byte order mark)][wiki-bom]」來判斷資料屬於哪種 endianness。BOM 是一個 Unicode magic number,通常放置在 text stream 的最前端。不過,並不是每個資料都會加上這個 header,而且有時候我們不需要 BOM 資訊,使用資料前還必須先 [strip bom][npm-strip-bom],說實話挺麻煩的。

    ### Data Structure Alignment

    要接觸底層的記憶體,免不了瞭解 CPU 如何從記憶體中讀取資料,記憶體底層到底如何配置。

    一般來說,現代的 CPU 通常設計以 word 為單位(例如 4 bytes)讀寫記憶體裡的資料,而資料對齊(data aligment)則是將資料放置在 word-size * n 倍的記憶體位址上,使 CPU 以最為有效率的方式讀寫。那為什麼對齊 word-size 會最有效率呢?先假設有一個 C struct 如下:
    一般來說,現代的 CPU 通常設計以 word 為單位(例如 4 bytes)讀寫記憶體裡的資料,而資料對齊(data aligment)則是將資料放置在 word-size * n 倍的記憶體位址上,使 CPU 以最為有效率的方式讀寫。那為什麼對齊 word-size 會最有效率呢?假設有個 C struct 如下:

    ```c
    struct AlignDemo {
    @@ -312,7 +316,7 @@ i = int 所佔的 byte
    | [c] [i] [i] [i] | [i] [s] [s] [ ] |
    ```

    前面提到 CPU 是以 word-size 存取記憶體上的資料,當我們嘗試讀取 char 和 short 時並沒有什麼問題,CPU 只需要取一次 word chunk 就可以。然而,當我想要取得 int 時,CPU 需先取第一個 data chunk 以獲取前三個 bytes,再取第二個 word chunk 並 shift 資料,以取得最後一個 bytes。多餘的記憶體存取會造成 CPU 額外的負擔。
    前面提到 CPU 是以 word-size 存取記憶體上的資料,當嘗試讀取 char 和 short 時並沒有什麼問題,CPU 只需取一次 word chunk 再 offset 就可取得正確的值。然而,當欲讀取 int 時,CPU 需先取第一個 data chunk 以獲取 int 前三個 bytes,再取第二個 word chunk 並 shift 資料,以取得 int 最後一個 byte。如此多餘的記憶體存取會造成 CPU 額外的負擔。

    解決方法是 **Data Structure Padding**,也就是在資料無法對齊 word-size 時,加上一些填充用的成員。

    @@ -399,7 +403,7 @@ Blob 的 spec 寫在 [W3C File API draft][w3c-blob] 中,為 `File` class 的

    ## Wrap-up

    藉由這些直接操作 binary data 的 API,現代的 JavaScript 環境的效能提升到另一個層次,若再配合 Web worker Service worker 等多線程技術,加上線程共享的 `ShareArrayBuffer``Atomic` API,高效能的 web app 指日可待。如果再加上逐漸普及,[即將成為 Webpack 一等公民][medium-webpack-awarded][WebAssembly][wasm],JavaScript/Web 的世界更是不可限量啊,使用 Rust 寫網頁的世代即將來臨 XD。
    藉由這些直接操作 binary data 的 API,現代的 JavaScript 環境的效能提升到另一個層次,若再配合 Web worker Service worker 等多線程技術,加上線程共享的 `ShareArrayBuffer``Atomic` API,高效能的 web app 指日可待。如果再加上逐漸普及,[即將成為 Webpack 一等公民][medium-webpack-awarded][WebAssembly][wasm],JavaScript/Web 的世界更是不可限量啊!或許,使用 Rust 寫網頁的世代即將來臨 XD。

    前端工程師們,活到老,學到掛吧!

  3. weihanglo revised this gist Sep 24, 2017. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion binary-data-manipulations-in-javascript.md
    Original file line number Diff line number Diff line change
    @@ -401,7 +401,7 @@ Blob 的 spec 寫在 [W3C File API draft][w3c-blob] 中,為 `File` class 的

    藉由這些直接操作 binary data 的 API,現代的 JavaScript 環境的效能提升到另一個層次,若再配合 Web worker Service worker 等多線程技術,加上線程共享的 `ShareArrayBuffer``Atomic` API,高效能的 web app 指日可待。如果再加上逐漸普及,[即將成為 Webpack 一等公民][medium-webpack-awarded][WebAssembly][wasm],JavaScript/Web 的世界更是不可限量啊,使用 Rust 寫網頁的世代即將來臨 XD。

    前端工程師們,活到老,學到死吧
    前端工程師們,活到老,學到掛吧

    ## Reference

  4. weihanglo revised this gist Sep 24, 2017. 1 changed file with 2 additions and 8 deletions.
    10 changes: 2 additions & 8 deletions binary-data-manipulations-in-javascript.md
    Original file line number Diff line number Diff line change
    @@ -2,18 +2,12 @@

    ![](https://i.imgur.com/Mx1Rbus.png)

    本文介紹 JavaScript 開發者不可不知,操作 binary data 的靈魂,Typed Array 們!

    _(撰於 2017-09-03,基於 ECMAScript 6+,Node.js 8.3)_

    <!-- more -->

    ## Introduction

    由於高度封裝與抽象, JavaScript 的執行效率比不上 C 的語言。例如 JavaScript 的 Array 下標(subscript)是根據 hash key 而非實體記憶體位址 offset 取值,雖然方便,卻多了效能開銷。當 Canvas、WebGL、WebVR 開始走紅,效能越來越受重視,如何讓 JavaScript 達到如同 C 指標般操作 binary data 變得至關重要。

    存在許久但最近才變為 ES6 標準「**Typed Array**」就是解放 JavaScript 操作 binary data 能力的好工具!一起來了解 Typed Array 吧!

    _(撰於 2017-09-03,基於 ECMAScript 6+,Node.js 8.3)_

    ## Buffer v.s View

    ES6 引入的 [Typed Array 家族][mdn-typedarrays],可以分為兩大類:**Buffer****View**
  5. weihanglo revised this gist Sep 9, 2017. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions binary-data-manipulations-in-javascript.md
    Original file line number Diff line number Diff line change
    @@ -287,7 +287,7 @@ Little-endian 和 Big-endian 可以視為不同的電腦(CPU)講不同的語

    **Q:那在 JavaScipt 要如何處理 endian?**

    如果不碰底層的記憶體操作,寫 JavaScript 是不用理會 endianness 的,但當你要操作 TypedArray 時,了解 data 的 endian 就至關重要了。`TypedArray` 預設是使用系統的 endianness,所以如果你接受一筆資料和系統的 endianness,TypedArray 便使不上力。而前面介紹到 DataView 的 byte getter/setter 最後一個參數就是用來決定以哪種 byte order 存取資料,預設是 Big-endian(`false`),透過切換這個 flag,任何 binary data 都橫看成嶺側成峰了。
    如果不碰底層的記憶體操作,寫 JavaScript 是不用理會 endianness 的,但當你要操作 TypedArray 時,了解 data 的 endian 就至關重要了。`TypedArray` 預設是使用系統的 endianness,所以如果你接收一筆資料,與你的系統的 endianness 不一致,TypedArray 便使不上力。而前面介紹到 DataView 的 byte getter/setter 最後一個參數就是用來決定以哪種 byte order 存取資料,預設是 Big-endian(`false`),透過切換這個 flag,任何 binary data 都橫看成嶺側成峰了。

    **Q:那我們要如何得知資料的 byte order?**

    @@ -397,7 +397,7 @@ new Uint32Array(buffer, 0)
    - `type`:blob 實例的 [MIME type][wiki-mime]
    - `slice`:切割一部分的 blob 實例,返回新的 blob。

    Blob 寫在 [W3C File API draft][w3c-blob] 中, `File` class 的父類別。主要目的是為了代表與儲存 JavaScript native 以外的格式,例如圖檔。Blob 除了可以從 object 建構,也可傳入 TypedArray 或 DOMString 建構。除了 File API,Fetch API、XMLHttpRequest v2 也都可以將 Request/Response 的 body 轉換成 Blob,非常泛用途呢!
    Blob 的 spec 寫在 [W3C File API draft][w3c-blob] 中, `File` class 的父類別。主要目的是提供可代表與儲存 JavaScript native 以外的格式,例如以 blob 儲存 **死肥宅.jpg**。Blob 除了可以從 object 建構,也可傳入 TypedArray 或 DOMString 建構。此外,File API,Fetch API、XMLHttpRequest v2 也都可以將 Request/Response 的 body 轉換成 Blob,非常泛用途呢!

    而 Blob 最強大的地方就是配合 `URL.createObjectURL` 生成一個 Blob URL。如同你我認知中的 URL,任何運用 URL 之處,都可以傳入 Blob URL,比起 `Image``ImageData``MediaSource`,URL 接受與使用度肯定更為廣闊,這讓資料處理,物件傳遞的耦合性變得更低。

  6. weihanglo revised this gist Sep 5, 2017. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion binary-data-manipulations-in-javascript.md
    Original file line number Diff line number Diff line change
    @@ -136,7 +136,7 @@ const u16 = new Uint16Array(4)
    const u8 = new Uint8Array(u16) // length of u8 is 4
    ```

    這樣會建立一個新的型別 array,但記憶體區塊不變。我們的例子中,u8 因為溢位的緣故(overflow),自動過濾偶數 bytes(或奇數,視 [wiki-endianness][wiki-endianness] 而定),僅顯示餘下 4 個 bytes 的資料,記憶體位址變得不連續。
    這樣會建立一個新的型別 array,但記憶體區塊不變。我們的例子中,u8 因為溢位的緣故(overflow),自動過濾偶數 bytes(或奇數,視 [endianness][wiki-endianness] 而定),僅顯示餘下 4 個 bytes 的資料,記憶體位址變得不連續。

    **從 ArrayBuffer 建立**

  7. weihanglo revised this gist Sep 5, 2017. 1 changed file with 3 additions and 3 deletions.
    6 changes: 3 additions & 3 deletions binary-data-manipulations-in-javascript.md
    Original file line number Diff line number Diff line change
    @@ -303,7 +303,7 @@ Little-endian 和 Big-endian 可以視為不同的電腦(CPU)講不同的語
    struct AlignDemo {
    char c; // 1 byte
    int i; // 4 bytes
    shor s; // 2 bytes
    short s; // 2 bytes
    };
    ```

    @@ -329,7 +329,7 @@ struct AlignDemo {
    char c;
    char padding_0[3]; // 填充用成員
    int i;
    shor s;
    short s;
    char padding_1[2];
    };

    @@ -352,7 +352,7 @@ p = padding 所佔的 byte
    struct AlignDemo {
    int i;
    char c;
    shor s;
    short s;
    char padding[0]
    };
    ```
  8. weihanglo revised this gist Sep 5, 2017. 1 changed file with 19 additions and 19 deletions.
    38 changes: 19 additions & 19 deletions binary-data-manipulations-in-javascript.md
    Original file line number Diff line number Diff line change
    @@ -10,25 +10,25 @@ _(撰於 2017-09-03,基於 ECMAScript 6+,Node.js 8.3)_

    ## Introduction

    由於高度封裝與抽象, JavaScript 的執行效率比不上 C 的語言。例如 JavaScript 的 Array 下標(subscript)是根據 hash key 而非實體記憶體位址 offset 取值,雖然方便,卻多了效能開銷。當 Canvas、WebGL、WebVR 開始走紅,效能越來越受重視,如何讓 JavaScript C 指標般操作 binary data 變得至關重要。
    由於高度封裝與抽象, JavaScript 的執行效率比不上 C 的語言。例如 JavaScript 的 Array 下標(subscript)是根據 hash key 而非實體記憶體位址 offset 取值,雖然方便,卻多了效能開銷。當 Canvas、WebGL、WebVR 開始走紅,效能越來越受重視,如何讓 JavaScript 達到如同 C 指標般操作 binary data 變得至關重要。

    存在許久但最近才變為 ES6 標準 **Typed Array** 就是解放 JavaScript 操作 binary data 能力的好工具!一起來了解 Typed Array 吧!
    存在許久但最近才變為 ES6 標準**Typed Array**就是解放 JavaScript 操作 binary data 能力的好工具!一起來了解 Typed Array 吧!

    ## Buffer v.s View

    ES6 引入的 [Typed Array 家族][mdn-typedarrays],可以分為兩大類 **Buffer****View**
    ES6 引入的 [Typed Array 家族][mdn-typedarrays],可以分為兩大類**Buffer****View**

    所謂 **Buffer** 是一個指向儲資料的記憶體區塊之物件,類似於 `malloc` 配置出來的空間,無法直接存取或修改 buffer 內部的資料,在 JavaScript 中 Buffer 的實作就是 `ArrayBuffer`
    所謂 **Buffer** 是一個指向儲存資料的記憶體區塊之物件,類似於 `malloc` 配置出來的空間,無法直接存取或修改 buffer 內部的資料,在 JavaScript 中 Buffer 的實作就是 `ArrayBuffer`

    如果我們想存取某些 buffer 底下的內容,我們需要 **View**(視圖),透過宣告不同資料型別的 view,電腦就會了解如何操作這段 data chunk,該當作 float32 讀取,抑或 unsigned integer 來操作。
    如果我們想存取某些 buffer 底下的內容,我們需要 **View**(視圖),透過宣告不同資料型別的 view,電腦就會了解如何操作這段 data chunk,該當作 float32 讀取呢?抑或以 unsigned integer 來操作。

    ES6 規範了三個 Typed Array 相關物件,對應類別如下:

    - `ArrayBuffer`:Buffer,代表一段記憶體區塊,僅能透過 View 操作其內容。
    - `TypedArray`:View,儲存固定型別資料的 Array,例如 `Uint8Array`(8-bit unsigned integer)、`Float64Array`(64-bit IEEE floating point number)。
    - `DataView`:View,不限制型別,可自定義從哪個 byte,以什麼型別,用哪種 byte order(endian)存取。

    ## `ArrayBuffer`
    ## ArrayBuffer

    [ArrayBuffer][mdn-arraybuffer] 代表一段固定大小的記憶體區塊,也稱為 byte-array。主要的功能就是配置實體記憶體來儲存 raw binary data。一般很少直接操作 ArrayBuffer,實際上也只能將其 reference 傳給其他物件,讓其他物件來處理/使用資料。

    @@ -78,13 +78,13 @@ function handleFiles (files) { // files -> FileList 物件,裡面有 File 實
    }
    ```

    ## `TypedArray`
    ## TypedArray

    TypedArray 並非任何一個型別,也非全域可取得的建構函數,而是一個抽象概念,對應到許多不同型別的 Array。老實說,TypedArray 這兩個字的命名其實已說明一切,我們說文解字,先從 Typed 講起。
    TypedArray 並非任何一個型別,也非全域可取得的建構函數,而是一個抽象概念,對應到許多不同型別的 Array 罷了。老實說,TypedArray 這個的命名已說明一切,讓我來說文解字,先從 Typed 講起。

    ### Types of TypedArray

    所謂的 Typed,意指「**限定型別**」,Array 中的元素都是同一種型別。有哪些型別呢?TypedArray 是為了操作 binary 而生,當然只有最底層的型別,我們可以根據需求決定每個元素該從 raw data 讀取多少與如何讀取 bytes。
    所謂的 Typed,意指「**限定型別**」,Array 中的元素都是同一種型別。有哪些型別呢?TypedArray 是為了操作 binary 而生,當然只有最底層以 bytes 為基礎,幾乎沒有什麼抽象概念的型別。我們可根據需求,決定每個元素該從 raw data 讀取多少與如何讀取 bytes。

    目前 ES6 定義以下幾種 typed array types:

    @@ -112,7 +112,7 @@ TypedArray 並非任何一個型別,也非全域可取得的建構函數,而

    ### Array-like Methods

    而 TypedArray 中的 Array,其實就是我們熟悉的 JavaScript Array,可視為「**在 ArrayBuffer 的資料之上,架一層可存取 Array API**」。你想得到的 method `map``filter``reduce` 幾乎應有盡有,而 `push``shift``unshift``splice` 這類會改變 Array 長度的 **Mutator methods** 沒有實作(就只是 buffer 的 reference,`pop` 後資料還是在那裡啊 XD)。
    而 TypedArray 中的 Array,其實就是我們熟悉的 JavaScript Array,可視為「**在 ArrayBuffer 的資料之上,架一層可存取資料的 Array API**」。你想得到的 method `map``filter``reduce` 幾乎應有盡有,而 `push``shift``unshift``splice` 這類會改變 Array 長度的 **Mutator methods** 沒有實作(就只是 buffer 的 reference,`pop` 後資料還是在那裡啊 XD)。

    比較好玩的是 `TypedArray#subarray`,和 `slice` 同樣是回傳陣列切片,`slice` 是回傳一個淺拷貝(shallow-copy))的新 Array,而 `subarray` 則是在同一個 buffer 繼續切片,`TypedArray#buffer` 會取得相同的 buffer,`TypedArray#byteOffset` 也是根據原始 buffer 計算 offset。

    @@ -136,7 +136,7 @@ const u16 = new Uint16Array(4)
    const u8 = new Uint8Array(u16) // length of u8 is 4
    ```

    這樣會建立一個新的型別 array,但記憶體區塊不變。我們的例子中,u8 因為溢位的緣故(overflow),自動過濾偶數 bytes(或奇數,視 [endianness][endianness] 而定),僅顯示餘下 4 個 bytes 的資料,記憶體位址變得不連續。
    這樣會建立一個新的型別 array,但記憶體區塊不變。我們的例子中,u8 因為溢位的緣故(overflow),自動過濾偶數 bytes(或奇數,視 [wiki-endianness][wiki-endianness] 而定),僅顯示餘下 4 個 bytes 的資料,記憶體位址變得不連續。

    **從 ArrayBuffer 建立**

    @@ -184,9 +184,9 @@ Uint8Array.of(0xff, 0x100)
    // 0
    ```

    > Note:underflow,處理方式也相同
    > Note:underflow 的處理方式與 overflow 相同
    ### What is `Uint8ClampedArray`
    ### What is Uint8ClampedArray

    Clamp 的本意是鉗子,在計算機科學中,通常意味將資料值限制在特定範圍間。而 `Uint8ClampedArray` 中,就是將元素值限制在 0 - 255。換句話說,就是處理溢位的規則與 `Uint8Array` 不同。**當 overflow 時,該值會等於最大值 255;當 underflow 時,該值會等於 0**

    @@ -238,7 +238,7 @@ salaryView[0] = 10000

    顧名思義,**DataView** 是一種建構在 buffer 之上的 view。與一般 TypedArray 不同的是,建構 DataView 時並不會固定的資料型別,取而代之的是存取 data 時,必須明確的指定從哪個 byte offset 取哪一種 data type 出來。

    我們借用前例的複合資料來示範`DataView` 如何針對每個 bytes 處理自定義的資料。
    借用前例的複合資料來示範`DataView` 如何針對每個 bytes 處理自定義的資料。

    ```javascript
    const dv = new DataView(buffer)
    @@ -251,15 +251,15 @@ dv.setFloat32(8, 200000, true)
    dv.getFloat32(8, true) // 加薪囉!!
    ```

    各位有沒有注意到 DataView 的 bytes getter/setter 最後面都多帶了一個 boolean 參數?這個參數是指定使用 Little-endian 讀取資料,預設為 `false` 也就是以 Big-endian 讀取。可控制 endian 是 DataView 蠻重要但也頗惱人的特性,在下一節會介紹 Endianness。
    各位有沒有注意到DataView 的 bytes getter/setter 最後面都多帶了一個 boolean 參數?這個參數是指定使用 Little-endian 讀取資料,預設為 `false` 也就是以 Big-endian 讀取。可控制 endian 是 DataView 蠻重要但也頗惱人的特性,在下一節會介紹 Endianness。

    DataView 另一個重要特性就是不會 [buffer overflow][wiki-buffer-overflow],所謂的 buffer overflow 是當寫入一筆資料到指定 buffer 中,萬一寫入的資料大小超過該 buffer 的 boundary,溢出值就會覆寫下個的 byte。buffer overflow 同時是許多駭客的攻擊手法,有潛在的安全性問題。而透過 DataView setter 賦予一個超過型別最大值的數字,並不會覆蓋臨近記憶體位址的資料,而是內部先檢查邊界,處理 overflow 之後,再寫入該記憶體區間。

    ## Precautions

    Typed Arrays 幾乎可以做到如同 C 語言般的記憶體操作,十分強大。不過越是自由的 API,就代表要學習更多知識,注意更多細節,以下是操作 TypedArray 該銘記在心的事情:

    - [Endianess (Byte order)][wiki-endianness]
    - [Endianness (Byte order)][wiki-endianness]
    - [Data Structure Alignment][wiki-alignment]

    ### Endianness (Byte order)
    @@ -291,7 +291,7 @@ Little-endian 和 Big-endian 可以視為不同的電腦(CPU)講不同的語

    **Q:那我們要如何得知資料的 byte order?**

    如果資料是自己內部系統使用,其實溝通好就好,用 Mixed-endian 也不會有人管你。但如果是外界得來的任意資料,有時候我們可以透過 [BOM(byte order mark)][wiki-bom]來判斷資料屬於那種 endianness。BOM 是一個 Unicode magic number,通常放置在 text stream 的最前端。不過並不是每一個資料都會加上這個 header,有時候必須先 [strip bom][npm-strip-bom],說實話挺麻煩的。
    如果資料是自己家內部系統使用,其實溝通好就 OK,用 Mixed-endian 也不會有人管你。但如果是外界得來的任意資料,我們可以透過 [BOM(byte order mark)][wiki-bom]來判斷資料屬於那種 endianness。BOM 是一個 Unicode magic number,通常放置在 text stream 的最前端。不過,並不是每個資料都會加上這個 header,而且有時候我們不需要 BOM 資訊,使用資料前還必須先 [strip bom][npm-strip-bom],說實話挺麻煩的。

    ### Data Structure Alignment

    @@ -318,7 +318,7 @@ i = int 所佔的 byte
    | [c] [i] [i] [i] | [i] [s] [s] [ ] |
    ```

    前面提到 CPU 是以 word-size 存取記憶體上的資料,當我們嘗試讀取 char 和 short 時並沒有什麼問題,CPU 只需要取一次 word chunk 就可以。然而,當我想要取得 int 時,CPU 需先取第一個 data chunk 以獲取前三個 bytes,再取第二個 word chunk 並 shift 資料,以取得最後一個 bytes。多餘的記憶體存取 會造成 CPU 額外的負擔。
    前面提到 CPU 是以 word-size 存取記憶體上的資料,當我們嘗試讀取 char 和 short 時並沒有什麼問題,CPU 只需要取一次 word chunk 就可以。然而,當我想要取得 int 時,CPU 需先取第一個 data chunk 以獲取前三個 bytes,再取第二個 word chunk 並 shift 資料,以取得最後一個 bytes。多餘的記憶體存取會造成 CPU 額外的負擔。

    解決方法是 **Data Structure Padding**,也就是在資料無法對齊 word-size 時,加上一些填充用的成員。

    @@ -401,7 +401,7 @@ Blob 寫在 [W3C File API draft][w3c-blob] 中,時 `File` class 的父類別

    而 Blob 最強大的地方就是配合 `URL.createObjectURL` 生成一個 Blob URL。如同你我認知中的 URL,任何運用 URL 之處,都可以傳入 Blob URL,比起 `Image``ImageData``MediaSource`,URL 接受與使用度肯定更為廣闊,這讓資料處理,物件傳遞的耦合性變得更低。

    當我們建立 Blob URL 之後,若之後不需要用到該 URL,就該使用 `URL.revokeObjectURL` 取消註冊,否則該 URL 指向的 Blob 會持續留存,佔用你的儲存空間,直到瀏覽器執行 unload document cleanup 的步驟(如關閉分頁),才會將所有 Blob URL 清除。所以說,如需管理 Blob URL,還是老老實實把這些 URL 記錄起來吧!
    當我們建立 Blob URL 後,若可預期的未來內不需要用到該 URL,就使用 `URL.revokeObjectURL` 取消註冊,否則該 URL 指向的 Blob 會持續留存,佔用你的儲存空間,直到瀏覽器執行 unload document cleanup 的步驟(如關閉分頁),才會將所有 Blob URL 清除。所以說,如需管理 Blob URL,還是老老實實把這些 URL 記錄起來吧!

    ## Wrap-up

  9. weihanglo revised this gist Sep 4, 2017. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion binary-data-manipulations-in-javascript.md
    Original file line number Diff line number Diff line change
    @@ -405,7 +405,7 @@ Blob 寫在 [W3C File API draft][w3c-blob] 中,時 `File` class 的父類別

    ## Wrap-up

    藉由這些 binary data API,現代的 JavaScript 環境的效能以非同小可,若在考慮 web workers 等技術,配合多數瀏覽器尚未實作的 `ShareArrayBuffer``Atomic`,多線程高效能的 web app 指日可待,更別提逐漸普及,甚至[即將成為 Webpack 一等公民][medium-webpack-awarded][WebAssembly][wasm],使用 Rust 寫網頁的世代即將來臨。
    藉由這些直接操作 binary data API,現代的 JavaScript 環境的效能提升到另一個層次,若再配合 Web worker Service worker 等多線程技術,加上線程共享的 `ShareArrayBuffer``Atomic` API,高效能的 web app 指日可待。如果再加上逐漸普及,[即將成為 Webpack 一等公民][medium-webpack-awarded][WebAssembly][wasm]JavaScript/Web 的世界更是不可限量啊,使用 Rust 寫網頁的世代即將來臨 XD

    前端工程師們,活到老,學到死吧!

  10. weihanglo revised this gist Sep 3, 2017. 1 changed file with 4 additions and 4 deletions.
    8 changes: 4 additions & 4 deletions binary-data-manipulations-in-javascript.md
    Original file line number Diff line number Diff line change
    @@ -16,7 +16,7 @@ _(撰於 2017-09-03,基於 ECMAScript 6+,Node.js 8.3)_

    ## Buffer v.s View

    ES6 引入的 Typed Array 家族,可以分為兩大類 **Buffer****View**
    ES6 引入的 [Typed Array 家族][mdn-typedarrays],可以分為兩大類 **Buffer****View**

    所謂 **Buffer** 是一個指向儲資料的記憶體區塊之物件,類似於 `malloc` 配置出來的空間,無法直接存取或修改 buffer 內部的資料,在 JavaScript 中 Buffer 的實作就是 `ArrayBuffer`

    @@ -30,7 +30,7 @@ ES6 規範了三個 Typed Array 相關物件,對應類別如下:

    ## `ArrayBuffer`

    ArrayBuffer 代表一段固定大小的記憶體區塊,也稱為 byte-array。主要的功能就是配置實體記憶體來儲存 raw binary data。一般很少直接操作 ArrayBuffer,實際上也只能將其 reference 傳給其他物件,讓其他物件來處理/使用資料。
    [ArrayBuffer][mdn-arraybuffer] 代表一段固定大小的記憶體區塊,也稱為 byte-array。主要的功能就是配置實體記憶體來儲存 raw binary data。一般很少直接操作 ArrayBuffer,實際上也只能將其 reference 傳給其他物件,讓其他物件來處理/使用資料。

    建立一個 ArrayBuffer 有非常多種方法,可以直接配置,

    @@ -259,8 +259,8 @@ DataView 另一個重要特性就是不會 [buffer overflow][wiki-buffer-overflo

    Typed Arrays 幾乎可以做到如同 C 語言般的記憶體操作,十分強大。不過越是自由的 API,就代表要學習更多知識,注意更多細節,以下是操作 TypedArray 該銘記在心的事情:

    - Endianess (Byte order)
    - Data Structure Alignment
    - [Endianess (Byte order)][wiki-endianness]
    - [Data Structure Alignment][wiki-alignment]

    ### Endianness (Byte order)

  11. weihanglo revised this gist Sep 3, 2017. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions binary-data-manipulations-in-javascript.md
    Original file line number Diff line number Diff line change
    @@ -1,9 +1,9 @@
    # Binary Data Manipulations in JavaScript

    本文介紹 JavaScript 開發者不可不知,操作 binary data 的靈魂,Typed Array 們!

    ![](https://i.imgur.com/Mx1Rbus.png)

    本文介紹 JavaScript 開發者不可不知,操作 binary data 的靈魂,Typed Array 們!

    _(撰於 2017-09-03,基於 ECMAScript 6+,Node.js 8.3)_

    <!-- more -->
  12. weihanglo revised this gist Sep 3, 2017. 2 changed files with 2 additions and 0 deletions.
    2 changes: 2 additions & 0 deletions binary-data-manipulations-in-javascript.md
    Original file line number Diff line number Diff line change
    @@ -2,6 +2,8 @@

    本文介紹 JavaScript 開發者不可不知,操作 binary data 的靈魂,Typed Array 們!

    ![](https://i.imgur.com/Mx1Rbus.png)

    _(撰於 2017-09-03,基於 ECMAScript 6+,Node.js 8.3)_

    <!-- more -->
    Binary file removed ls-binary.png
    Binary file not shown.
  13. weihanglo created this gist Sep 3, 2017.
    440 changes: 440 additions & 0 deletions binary-data-manipulations-in-javascript.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,440 @@
    # Binary Data Manipulations in JavaScript

    本文介紹 JavaScript 開發者不可不知,操作 binary data 的靈魂,Typed Array 們!

    _(撰於 2017-09-03,基於 ECMAScript 6+,Node.js 8.3)_

    <!-- more -->

    ## Introduction

    由於高度封裝與抽象, JavaScript 的執行效率比不上 C 的語言。例如 JavaScript 的 Array 下標(subscript)是根據 hash key 而非實體記憶體位址 offset 取值,雖然方便,卻多了效能開銷。當 Canvas、WebGL、WebVR 開始走紅,效能越來越受重視,如何讓 JavaScript 像 C 指標般操作 binary data 變得至關重要。

    存在許久但最近才變為 ES6 標準 **Typed Array** 就是解放 JavaScript 操作 binary data 能力的好工具!一起來了解 Typed Array 吧!

    ## Buffer v.s View

    ES6 引入的 Typed Array 家族,可以分為兩大類 **Buffer****View**

    所謂 **Buffer** 是一個指向儲資料的記憶體區塊之物件,類似於 `malloc` 配置出來的空間,無法直接存取或修改 buffer 內部的資料,在 JavaScript 中 Buffer 的實作就是 `ArrayBuffer`

    如果我們想存取某些 buffer 底下的內容,我們需要 **View**(視圖),透過宣告不同資料型別的 view,電腦就會了解如何操作這段 data chunk,該當作 float32 讀取,抑或 unsigned integer 來操作。

    ES6 規範了三個 Typed Array 相關物件,對應類別如下:

    - `ArrayBuffer`:Buffer,代表一段記憶體區塊,僅能透過 View 操作其內容。
    - `TypedArray`:View,儲存固定型別資料的 Array,例如 `Uint8Array`(8-bit unsigned integer)、`Float64Array`(64-bit IEEE floating point number)。
    - `DataView`:View,不限制型別,可自定義從哪個 byte,以什麼型別,用哪種 byte order(endian)存取。

    ## `ArrayBuffer`

    ArrayBuffer 代表一段固定大小的記憶體區塊,也稱為 byte-array。主要的功能就是配置實體記憶體來儲存 raw binary data。一般很少直接操作 ArrayBuffer,實際上也只能將其 reference 傳給其他物件,讓其他物件來處理/使用資料。

    建立一個 ArrayBuffer 有非常多種方法,可以直接配置,

    ```javascript
    // 直接配置 8 bytes,初始值為 0 的記憶體區塊
    const buffer = new ArrayBuffer(8)

    // 利用 `slice` 將某些 bytes 複製到另一個 ArrayBuffer
    // 這裡複製 `buffer` 倒數四個 bytes 到 bufferCopied 中
    const bufferCopied = buffer.slice(-4)
    ```

    或是最常使用的,HTTP response 選擇接收 buffer,

    ```javascript
    // XMLHttpRequest 指定 resopnseType (XMLHttpRequest v2)
    const xhr = new XMLHttpRequest()
    xhr.open('GET', '/path/to/黑人問號.jpg', true)
    xhr.responseType = 'arraybuffer' // 將 reponse 型別設定為 arraybuffer
    xhr.onload = function(e) {
    console.log(this.response) // this.response 為 ArrayBuffer
    }
    xhr.send()

    // Fetch API 也提供 Body#arrayBuffer 的方法轉換 Request/Response stream body
    const response = await fetch('/path/to/柯P火影.gif')
    const buffer = await response.arrayBuffer() // 取得 ArrayBuffer 實例
    console.log(buffer.byteLength) // 查看當前這個 ArrayBuffer 有多少 bytes。
    ```

    當然,也可以透過 `File``FileReader` API,讀取使用者上傳的資料。

    ```javascript
    const input = document.querySelector('input')
    input.addEventListener('change', handleFiles, false);

    function handleFiles (files) { // files -> FileList 物件,裡面有 File 實例
    if (files[0]) {
    const reader = new FileReader()
    reader.onload = function ({ target: { result } }) {
    console.log(result.byteLength) // result 是一個 ArrayBuffer
    }
    reader.readAsArrayBuffer = files[0] // File 是特殊的 Blob 型別
    }
    }
    ```

    ## `TypedArray`

    TypedArray 並非任何一個型別,也非全域可取得的建構函數,而是一個抽象概念,對應到許多不同型別的 Array。老實說,TypedArray 這兩個字的命名其實已說明一切,我們說文解字,先從 Typed 講起。

    ### Types of TypedArray

    所謂的 Typed,意指「**限定型別**」,Array 中的元素都是同一種型別。有哪些型別呢?TypedArray 是為了操作 binary 而生,當然只有最底層的型別,我們可以根據需求決定每個元素該從 raw data 讀取多少與如何讀取 bytes。

    目前 ES6 定義以下幾種 typed array types:

    | 型別 | bytes/元素 | 對應 C 語言 |
    | ------------------- | :------: | :--------: |
    | `Int8Array` | 1 | int8_t |
    | `Uint8Array` | 1 | uint8_t |
    | `Uint8ClampedArray` | 1 | uint8_t |
    | `Int16Array` | 2 | int16_t |
    | `Uint16Array` | 2 | uint16_t |
    | `Int32Array` | 4 | int32_t |
    | `Uint32Array` | 4 | uint32_t |
    | `Float32Array` | 4 | float |
    | `Float64Array` | 8 | double |

    實際上,TypedArray 本身並並不儲存任何 buffer 資料,只保存該 buffer 的 reference,我們可以透過 `TypedArray#buffer` 獲取原始的 ArrayBuffer。也因此,同一個 ArrayBuffer 可以建構出多個不同的 TypedArray。可視為「**從不同視角解讀 ArrayBuffer 中的 binary data**」。

    例如下圖是一個 16 bytes 的 ArrayBuffer,我們可以透過它,建立多個不同型別的 TypedArray。

    ![](https://mdn.mozillademos.org/files/8629/typed_arrays.png)

    不同型別的 TypedArray 的元素對應到不同的 byte 數量,這項資訊會記錄在 `TypedArray#BYTES_PER_ELEMENT` property 上。例如:Uint8Array 一個元素對應到一個 byte,Float64Array 則對應到 8 bytes。

    如果還是無法理解,其實可以將 TypedArray 想像為 C 語言的 `void *ptr`,在存取、遍歷不同 data type 的 Array 時,轉型(cast)成不同的型別,讓指標根據不同 data type 的 size 做對應 offset。

    ### Array-like Methods

    而 TypedArray 中的 Array,其實就是我們熟悉的 JavaScript Array,可視為「**在 ArrayBuffer 的資料之上,架一層可存取 Array API**」。你想得到的 method `map``filter``reduce` 幾乎應有盡有,而 `push``shift``unshift``splice` 這類會改變 Array 長度的 **Mutator methods** 沒有實作(就只是 buffer 的 reference,`pop` 後資料還是在那裡啊 XD)。

    比較好玩的是 `TypedArray#subarray`,和 `slice` 同樣是回傳陣列切片,`slice` 是回傳一個淺拷貝(shallow-copy))的新 Array,而 `subarray` 則是在同一個 buffer 繼續切片,`TypedArray#buffer` 會取得相同的 buffer,`TypedArray#byteOffset` 也是根據原始 buffer 計算 offset。

    ### Consturct a TypedArray

    建構 TypedArray 非常簡單,選擇好 data type 之後,`new` 一個就完成了!

    **直接初始化**

    建立一個 4 * 2 bytes 初始值為 0 的 Uint16Array

    ```javascript
    const u16 = new Uint16Array(4)
    ```

    **從 TypedArray 建立**

    我們也可以從其他 TypedArray 建立相同長度的 TypedArray,會指向同一個 buffer。

    ```javascript
    const u8 = new Uint8Array(u16) // length of u8 is 4
    ```

    這樣會建立一個新的型別 array,但記憶體區塊不變。我們的例子中,u8 因為溢位的緣故(overflow),自動過濾偶數 bytes(或奇數,視 [endianness][endianness] 而定),僅顯示餘下 4 個 bytes 的資料,記憶體位址變得不連續。

    **從 ArrayBuffer 建立**

    不過,也可以透過 `TypedArray#buffer` 取得並共享當前的 buffer,該 array 的記憶體區間就會是連續的。

    ```javascript
    // 因為 buffer 總共有 4 * 2 = 8 bytes,所以 u8_continuous 長度為 8
    const u8_continuous = new Uint8Array(u16.buffer)
    ```

    當然,ArrayBuffer 可直接配置一塊記憶體區塊,並使用它建構 TypedArray,甚至透過 `length``byteOffset` 指定該 buffer 不同的區間來建構。

    ```javascript
    const buffer = new ArrayBuffer(16)
    const i32 = new Int32Array(buffer) // 32 * 2 bytes

    // 從 4 bytes offset 的位址開始,切一個長度為 7 bytes 的 array。
    const i8 = new Iint8Array(buffer, 4, 7)
    ```

    ### Overflow

    如同 C 語言,不同類型的 TypedArray 可以容納的 bytes 範圍是固定的,超過此一範圍,就會出現[「溢位(Overflow)」][wiki-integer-overflow],例如 Uint8Array 中僅能放入 1 byte = 8 bits 的資料,如果放入 `0x100`(256,9 bits),就會溢位。

    那溢位後,資料會怎麼呈現呢?

    每個語言實作不盡相同,TypedArray 的溢位處理規則和多數語言相同:**捨棄溢出的 high bits。** 我們來看簡單的例子。

    ```javascript
    Uint8Array.of(0xff, 0x100)
    // Unit8Array [255, 0]
    ```

    第一個例子中,我們選用 Uint8Array,一個元素最多儲存 8 bits 的資料,第二個元素是 256,需要第 9 bit 來儲存,因此溢位。

    ```javascript
    // 255,至少需要 8 bits 儲存
    0b11111111

    // 256,至少需要 9 bits 儲存
    0b100000000
    //└── 這個 1 溢位,將被捨棄,僅保留最低有效的 8 bits,計算結果為 `0`
    // 捨棄的方式同於 bitwise or `& 0xFF`
    0x100 & 0xFF
    // 0
    ```

    > Note:underflow,處理方式也相同。
    ### What is `Uint8ClampedArray`

    Clamp 的本意是鉗子,在計算機科學中,通常意味將資料值限制在特定範圍間。而 `Uint8ClampedArray` 中,就是將元素值限制在 0 - 255。換句話說,就是處理溢位的規則與 `Uint8Array` 不同。**當 overflow 時,該值會等於最大值 255;當 underflow 時,該值會等於 0**

    ```javascript
    Uint8Array.of(0xff, 0x100, -100)
    // Unit8Array [255, 0, 156]
    Uint8ClampedArray.of(0xff, 0x100, -100)
    // Uint8ClampedArray [255, 255, 0]
    ```

    這有什麼好處呢?在影像處理上非常方便。有個很常舉的例子,有 3 bytes 的 Uint8Array 存放 RGB 色碼,我們想要增加他的 gamma factor,如果使用 Uint8Array 儲存:

    ```javascript
    // 必須自行限制大小,防止 ooverflow/underflow。
    u8[i] = Math.max(0, Math.min(255, u8[i] * gamma)) // u8 是一個 Uint8Array
    ```

    如果是 `Uint8ClampedArray`,只需要直接乘上 gammer factor,非常方便。

    ```javascript
    pixels[i] *= gamma // pixels 是一個 Uint8ClampedArray
    ```

    ### Composite Data Structure

    當需處理類似 C struct 的複合資料結構,如下所示

    ```c
    struct employee {
    unsigned int id; // 4 * 1 bytes
    unsigned char department[4]; // 1 * 4 bytes
    float salary; // 4 * 1 bytes
    };
    ```

    我們可以宣告對應的 TypedArray 來處理。模擬出如果 C struct 的資料結構。

    ```javascript
    const buffer = new ArrayBuffer(12)
    const idView = new Uint32Array(buffer, 0, 1)
    const deptView = new Uint8Array(buffer, 4, 4)
    const salaryView = new Float32Array(buffer, 8)
    idView[0] = 123
    deptView.forEach((_, i) => { deptView[i] = i * i })
    salaryView[0] = 10000
    ```

    ## DataView

    顧名思義,**DataView** 是一種建構在 buffer 之上的 view。與一般 TypedArray 不同的是,建構 DataView 時並不會固定的資料型別,取而代之的是存取 data 時,必須明確的指定從哪個 byte offset 取哪一種 data type 出來。

    我們借用前例的複合資料來示範,`DataView` 如何針對每個 bytes 處理自定義的資料。

    ```javascript
    const dv = new DataView(buffer)
    // 從 byte offset 0 的位址開始取 Uint32 的資料
    // 取得 ID -> 123
    dv.getUint32(0, true)

    // 從 byte offset 8 的位址開始寫入 Float32 的資料
    dv.setFloat32(8, 200000, true)
    dv.getFloat32(8, true) // 加薪囉!!
    ```

    各位有沒有注意到 DataView 的 bytes getter/setter 最後面都多帶了一個 boolean 參數?這個參數是指定使用 Little-endian 讀取資料,預設為 `false` 也就是以 Big-endian 讀取。可控制 endian 是 DataView 蠻重要但也頗惱人的特性,在下一節會介紹 Endianness。

    DataView 另一個重要特性就是不會 [buffer overflow][wiki-buffer-overflow],所謂的 buffer overflow 是當寫入一筆資料到指定 buffer 中,萬一寫入的資料大小超過該 buffer 的 boundary,溢出值就會覆寫下個的 byte。buffer overflow 同時是許多駭客的攻擊手法,有潛在的安全性問題。而透過 DataView setter 賦予一個超過型別最大值的數字,並不會覆蓋臨近記憶體位址的資料,而是內部先檢查邊界,處理 overflow 之後,再寫入該記憶體區間。

    ## Precautions

    Typed Arrays 幾乎可以做到如同 C 語言般的記憶體操作,十分強大。不過越是自由的 API,就代表要學習更多知識,注意更多細節,以下是操作 TypedArray 該銘記在心的事情:

    - Endianess (Byte order)
    - Data Structure Alignment

    ### Endianness (Byte order)

    在計算機科學領域下,Data 是一個物理概念,指儲存在電腦記憶體上的一個 bits/bytes 序列。Data 本身並沒有任何意義,想使用它,必須自行解讀出抽象的意義,例如將 Data 讀取為字串或數字。

    我們知道記憶體是基於位址(address)依序儲存 Data,每個位址可以存上 1 byte data。如果使用 `Uint8Array` 這種一次存取一個 byte 的格式,那麼完全不會有任何問題,怎麼存取,都是依連續的記憶體位址順序:

    ```
    --- 資料讀取順序 -->
    | Offset | 0 | 1 | 2 | 3 |
    | ------ | ---- | ---- | ---- | ---- |
    | Data | 0x11 | 0x22 | 0x33 | 0x44 |
    ```

    我們會得到 `[17, 34, 51, 68]` 的 Array。

    當我們需要一次存取多 bytes,例如這個範例的記憶體區塊其實是一個 32 bit 的整數,那實際代表的數字會是多大?是 `0x11223344`(十進位:287454020) 嗎?

    這其實牽扯到 CPU 的設計,目前市面上的多數 CPU 處理 multi-bytes 的資料時,大多從「最低有效位(LSB,least significant byte)」,也就是從權重最小的位數開始寫入。所以讀取這筆資料時,最前面的記憶體 offset 就是最權重最小的 bytes,所以最後會得到 `0x44332211`(十進位:1144201745)。這種存取排序,我們稱之為 **Little-endian**

    有最低,當然就有最高,**Big-endian** 則是從「最高有效位(MSB,most significant byte)」開始存取。因此照著記憶體位址依序讀取,會得到跟記憶體 offset 順序相同的 `0x11223344`。Big-endian 雖然在個人電腦中不常見,但許多網路協議和設備都是採用 Big-endian 存取資料,佔有一定的重要性。

    Little-endian 和 Big-endian 可以視為不同的電腦(CPU)講不同的語言,一個從右到左,另一個從左到右。其實在人類日常生活中也可以看到相同的現象,例如歐洲常用的日期格式為 day-month-year,ISO 國際標準則反之 year-month-day,所以,當你看到一個 17-09-07 的日期時,必須先判斷是否為 ISO 的標準,才能知道這場約會是在下禮拜的九月七日,抑或你需要一台時光機回到過去。

    **Q:那在 JavaScipt 要如何處理 endian?**

    如果不碰底層的記憶體操作,寫 JavaScript 是不用理會 endianness 的,但當你要操作 TypedArray 時,了解 data 的 endian 就至關重要了。`TypedArray` 預設是使用系統的 endianness,所以如果你接受一筆資料和系統的 endianness,TypedArray 便使不上力。而前面介紹到 DataView 的 byte getter/setter 最後一個參數就是用來決定以哪種 byte order 存取資料,預設是 Big-endian(`false`),透過切換這個 flag,任何 binary data 都橫看成嶺側成峰了。

    **Q:那我們要如何得知資料的 byte order?**

    如果資料是自己內部系統使用,其實溝通好就好,用 Mixed-endian 也不會有人管你。但如果是外界得來的任意資料,有時候我們可以透過 [BOM(byte order mark)][wiki-bom]來判斷資料屬於那種 endianness。BOM 是一個 Unicode magic number,通常放置在 text stream 的最前端。不過並不是每一個資料都會加上這個 header,有時候必須先 [strip bom][npm-strip-bom],說實話挺麻煩的。

    ### Data Structure Alignment

    要接觸底層的記憶體,免不了瞭解 CPU 如何從記憶體中讀取資料,記憶體底層到底如何配置。

    一般來說,現代的 CPU 通常設計以 word 為單位(例如 4 bytes)讀寫記憶體裡的資料,而資料對齊(data aligment)則是將資料放置在 word-size * n 倍的記憶體位址上,使 CPU 以最為有效率的方式讀寫。那為什麼對齊 word-size 會最有效率呢?先假設有一個 C struct 如下:

    ```c
    struct AlignDemo {
    char c; // 1 byte
    int i; // 4 bytes
    shor s; // 2 bytes
    };
    ```

    理論上的記憶體配置如下,總共需要 7 bytes 的記憶體空間。

    ```
    c = char 所佔的 byte
    s = short 所佔的 byte
    i = int 所佔的 byte
    | 0x000 | 0x020 |
    | [c] [i] [i] [i] | [i] [s] [s] [ ] |
    ```

    前面提到 CPU 是以 word-size 存取記憶體上的資料,當我們嘗試讀取 char 和 short 時並沒有什麼問題,CPU 只需要取一次 word chunk 就可以。然而,當我想要取得 int 時,CPU 需先取第一個 data chunk 以獲取前三個 bytes,再取第二個 word chunk 並 shift 資料,以取得最後一個 bytes。多餘的記憶體存取 會造成 CPU 額外的負擔。

    解決方法是 **Data Structure Padding**,也就是在資料無法對齊 word-size 時,加上一些填充用的成員。

    在我們的例子中,可以這樣做:

    ```c
    struct AlignDemo {
    char c;
    char padding_0[3]; // 填充用成員
    int i;
    shor s;
    char padding_1[2];
    };

    ```

    記憶體配置則如下:

    ```
    p = padding 所佔的 byte
    | 0x000 | 0x020 | 0x040 |
    | [c] [p] [p] [p] | [i] [i] [i] [i] | [s] [s] [p] [p] |
    ```

    本來只需要 7 bytes,對個齊後,反而用掉這麼多額外的 bytes,你玩我嗎?

    我們可以試著改變一下 struct member 的順序:

    ```c
    struct AlignDemo {
    int i;
    char c;
    shor s;
    char padding[0]
    };
    ```

    記憶體配置對應改變,只佔用 8 bytes。Brilliant!

    ```
    | 0x000 | 0x020 |
    | [i] [i] [i] [i] | [c] [s] [s] [p] |
    ```

    結構對齊(struct alignment)在 C 語言中是一門不小學問,除了結構內的成員本身要對齊,結構本身也要對齊。

    回到 JavaScript,當你在創建不同的 view 時,JavaScript engine 其實會進行簡單的 [natural alignment][stackoverflow-natural-align] 檢查。

    ```javascript
    const buffer = new ArrayBuffer(6)
    new Uint16Array(buffer, 1)
    // RangeError: start offset of Uint16Array should be a multiple of 2
    new Uint32Array(buffer, 0)
    // RangeError: byte length of Uint32Array should be a multiple of 4
    ```

    所以囉,當我們在設計複合資料時,想想對應的 C struct alignment,多考量記憶體底層,才不會讓操作 binary data 產生效能低落的反效果。

    ## Others

    最後,讓我們來認識除了 Typed Array 家族以外,JavaScript 的生態圈其他與記憶體息息相關的成員吧!

    ### Node.js `Buffer`

    早在 ES6 引入 TypedArray 之前,Node.js 為了處理 binary data,就實作 [Buffer class][nodejs-buffer],也針對 V8 引擎做最佳化。Buffer 在 Node.js 的環境中是 Global object,其功能可視為 ArrayBuffer + TypedArray + DataView 的複合體,甚至可以配置 non-zero-filled 的 unsafe buffer,好危險啊。

    在使用上,Buffer 可從 ArrayBuffer 建構,也可從自身建構 TypedArray。事實上,Node.js v3+ 之後,Buffer 就繼承自 `Uint8Array` 了,不過有些 memory share/copy 的實作與 spec 有出入,在與 TypedArray
    ArrayBuffer 轉換時,[需注意這些小細節][nodejs-buffer-caveats]

    ### Web API `Blob`

    [Blob][w3c-blob] 是一個不可變(immutable)的 raw binary sequence,只有兩個 attribute 和一個 method。

    - `size`:blob 實例的 byte 大小。
    - `type`:blob 實例的 [MIME type][wiki-mime]
    - `slice`:切割一部分的 blob 實例,返回新的 blob。

    Blob 寫在 [W3C File API draft][w3c-blob] 中,時 `File` class 的父類別。主要目的是為了代表與儲存 JavaScript native 以外的格式,例如圖檔。Blob 除了可以從 object 建構,也可傳入 TypedArray 或 DOMString 建構。除了 File API,Fetch API、XMLHttpRequest v2 也都可以將 Request/Response 的 body 轉換成 Blob,非常泛用途呢!

    而 Blob 最強大的地方就是配合 `URL.createObjectURL` 生成一個 Blob URL。如同你我認知中的 URL,任何運用 URL 之處,都可以傳入 Blob URL,比起 `Image``ImageData``MediaSource`,URL 接受與使用度肯定更為廣闊,這讓資料處理,物件傳遞的耦合性變得更低。

    當我們建立 Blob URL 之後,若之後不需要用到該 URL,就該使用 `URL.revokeObjectURL` 取消註冊,否則該 URL 指向的 Blob 會持續留存,佔用你的儲存空間,直到瀏覽器執行 unload document cleanup 的步驟(如關閉分頁),才會將所有 Blob URL 清除。所以說,如需管理 Blob URL,還是老老實實把這些 URL 記錄起來吧!

    ## Wrap-up

    藉由這些 binary data API,現代的 JavaScript 環境的效能以非同小可,若在考慮 web workers 等技術,配合多數瀏覽器尚未實作的 `ShareArrayBuffer``Atomic`,多線程高效能的 web app 指日可待,更別提逐漸普及,甚至[即將成為 Webpack 一等公民][medium-webpack-awarded][WebAssembly][wasm],使用 Rust 寫網頁的世代即將來臨。

    前端工程師們,活到老,學到死吧!

    ## Reference

    - [MDN: ArrayBuffer][mdn-arraybuffer]
    - [MDN: TypedArray][mdn-typedarrays]
    - [W3C: File API - Blob][w3c-blob]
    - [Wiki: Data structure alignment][wiki-alignment]
    - [Wiki: Endianness][wiki-endianness]
    - [Wiki: Integer overflow][wiki-integer-overflow]
    - [HTML5 Rocks: Typed Arrays][html5rocks-typedarray]
    - [Node.js: Buffer][nodejs-buffer]
    - [OPass:關於記憶體對齊(Alignment) ][opass-memory-alignment]
    - [阮一峰:ECMAScript 6 入门][rungyifeng-es6]

    [html5rocks-typedarray]: https://www.html5rocks.com/en/tutorials/webgl/typed_arrays/
    [mdn-arraybuffer]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer
    [mdn-typedarrays]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Typed_arrays
    [medium-webpack-awarded]: https://medium.com/webpack/webpack-awarded-125-000-from-moss-program-f63eeaaf4e15
    [nodejs-buffer-caveats]: https://nodejs.org/dist/latest-v8.x/docs/api/buffer.html#buffer_buffers_and_typedarray
    [nodejs-buffer]: https://nodejs.org/dist/latest-v8.x/docs/api/buffer.html
    [npm-strip-bom]: https://www.npmjs.com/package/strip-bom
    [opass-memory-alignment]: http://opass.logdown.com/posts/743054-about-memory-alignment
    [rungyifeng-es6]: http://es6.ruanyifeng.com/#docs/arraybuffer
    [stackoverflow-natural-align]: https://stackoverflow.com/a/25658188
    [w3c-blob]: https://w3c.github.io/FileAPI/#blob
    [wasm]: http://webassembly.org/
    [wiki-alignment]: https://en.wikipedia.org/wiki/Data_structure_alignment
    [wiki-bom]: https://en.wikipedia.org/wiki/Byte_order_mark#UTF-16
    [wiki-buffer-overflow]: https://en.wikipedia.org/wiki/Buffer_overflow
    [wiki-endianness]: https://en.wikipedia.org/wiki/Endianness
    [wiki-integer-overflow]: https://en.wikipedia.org/wiki/Integer_overflow
    [wiki-mime]: https://en.wikipedia.org/wiki/MIME
    Binary file added ls-binary.png
    Loading
    Sorry, something went wrong. Reload?
    Sorry, we cannot display this file.
    Sorry, this file is invalid so it cannot be displayed.