Underscore.js

**Underscore.js** 是一個綑綁常見功能的 JavaScript 函式庫。Underscore.js 提供的功能類似 Prototype.js 和 Ruby，但其使用函式語言程式設計而非基於原型編程。Underscore.js 的文件將自己稱為「與禮服（JQuery）和吊帶（Backbone.js）搭配的領帶（英語：the tie to go along with jQuery's tux, and Backbone.js' suspenders.）」。Underscore.js 由 Backbone.js 和 CoffeeScript 的建立者 Jeremy Ashkenas 建立。

## 歷史

2009 年底，為了順利開發 DocumentCloud，Jeremy Ashkenas 開發了 Underscore。
Underscore 為最早提供通用函數式編程實用程式的 JavaScript 函式庫之一，靈感來自 Prototype.js、Oliver Steele 的 Functional JavaScript 和 John Resig 的 Micro-Templating。

2012 年，John-David Dalton 建立 Underscore 的分叉 Lo-Dash（現在的 Lodash）。開發初期，Lo-Dash 被評價為「可客製化、效能佳和附加功能多」之 Underscore 的替代品。儘管如此，Lodash 在分叉早期階段便和 Underscore 的介面有不小的差異，甚至在 3.0.0 版本中開始更劇烈的變更，使得使用者必須要大量變更才能升級到新版本的 Lodash 或是從 Underscore 遷移到 Lodash。

2015 年 5 月，Jeremy Ashkenas 透露 John-David Dalton 已與他取得聯繫，希望將 Lodash 合併回 Underscore。縱然代碼風格和代碼大小可能會對合併產生困擾，Ashkenas 並不反對將 Lodash 的一些擴充內容合併到 Underscore。當時有幾個開發人員同時為 Underscore 和 Lodash 做出貢獻，這些貢獻者開始對 Underscore 進行更改，使其更像 Lodash。

然而，在眾人為此努力的同時，Dalton 對 Lodash 的介面進行了更大幅度的更改，並於 2015 年 6 月發布 Lodash 的版本 4.0.0。此更改使得 Lodash 與 Underscore 介面差距更大，也凸顯了其與 Lodash 本身的 3.x 系列版本地不小差異，同時此更改也促使一些依賴 Lodash 的項目分叉了自己的 Lodash 3 發行版。

2016 年 2 月，Dalton 宣布他認為合併工作已經完成，並建議 Underscore 使用者切換到 Lodash。然而，Underscore 的維護者明確表示，Underscore 依然會作為單獨的庫存在。兩個函式庫在 2016 年之後都進入了低開發活動狀態。

隨著時間的推移，較新版本的 ECMAScript 標準借鑒了 Underscore 的部分功能，例如 `Object.assign` 和 `Array.prototype.map`。儘管這些內建函式不如 Underscore 等效函式強大，此變更依然使得部分人認為 Underscore 不再為 JavaScript 項目增加價值。然而，新加入的數組功能只能在數組上使用，而非如同 Underscore 一樣可以適用任意迭代物件。除此之外，Underscore 的大部分函式仍然沒有內建對應函式。

截至 2021 年 3 月，Julian Gonggrijp 正在積極開發 Underscore，他於 2020 年 3 月開始做出重大貢獻。至今仍有許多函式庫依賴 Underscore，npm 上的每週下載次依然高達數百萬次。

## 內容

簡而言之，Underscore 提供了三大功能：

1. 100 多個泛用函式集合。文件 （頁面存檔備份，存於網際網路檔案館）區分出了幾個類別：
  * **集合函式**，例如 `find`、`map`、`min`／`max`、`groupBy` 和 `shuffle`，這些函式可以對迭代物件的元素進行操作。
  * **數組函式**，例如 `first`／`last`、`flatten`、`chunk` 和 `zip`，這些函式可以對類數組物件進行操作。
  * **函式函式**，例如 `bind`、`memoize`、`partial` 和 `debounce`，這些函式將函式作為參數並返回具有改變屬性的新函式（高階函式）。
  * **物件函式**為最基礎的類別，包含許多也在 Underscore 內部使用的函式。物件函式大致可以分為兩個子類：
    * **類型檢測函式**，例如 `isNumber`、`isElement` 和 `isDataView`。
    * **物件數據函式**，例如 `keys`、`extend`、`pick`／`omit`、`pairs` 和 `invert`，這些函式將一般物件作為數據進行操作。
  * **實用函式**是一個雜項類別，其中包括瑣碎功能如 `identity` 和 `noop` 和字串操作函式 `escape`、`unescape` 和 `template`。此類別還包括函式 `iteratee` 和 `mixin`，它們可以被視為第 2 點中提及的特殊工具。
2. 特殊工具，例如 `chain` 和 `iteratee`，這些特殊工具與第 1 點的函式相結合用以實現更短、更清晰的語法。以庫命名的特殊函式 `_` 是這些設施的核心。

需要閱讀的有文化的原始碼，以便很容易理解庫的實現方式。文件包括原始碼的彩現版本，其中註釋在左側，邏輯在右側。註釋使用 Markdown 格式化，邏輯有語法突顯。從 1.11 版本開始，Underscore 是模組化的。出於這個原因，文件現在包括帶註釋源的模組化版本，其中每個功能都在一個單獨的頁面上，並且 import 參照是可點擊的超鏈結，以及一個單一閱讀版本，其中所有功能都在一個頁面上依賴順序。

1. 使用文學編程進行編程，故程式碼較容易閱讀，且較容易理解函式庫的實現方式。Underscore 文件加入了源始碼的彩現版本，其中註釋在左側，邏輯在右側。註釋使用 Markdown 格式化，邏輯加上了語法突顯。自 1.11 版起，Underscore 進行了模組化，因此文件也同步加入了模組化版本 （頁面存檔備份，存於網際網路檔案館），每個功能都在一個單獨的頁面上，並且 import 參照是可點擊的超連結；同時也文件也提供了一個匯集所有模組的版本 （頁面存檔備份，存於網際網路檔案館），使用拓撲排序進行排序。

### 功能概述和範例

Underscore 使用函式語言程式設計，故而可以將多個函式混和成新的表達式。例如下方的程式碼便是使用兩個 Underscore 函式以使用第一個字元進行分組：

除了使用 Underscore 內建的函式，也可以自訂函式。例如下方程式碼實踐了自己的 `first` 函式，其結果和上方程式碼相同：

Underscore 內建不少這類型的函式，以便程式員可以從現有函式組合功能，而非每次都要自己實踐。

正常情況下，第一個參數會傳入迭代物件，第二個參數傳入迭代函式或 _iteratee_。上方範例中，`first` 是傳遞給 `groupBy` 的迭代函式。

iteratee 會接收三個參數：

1. 集合中當前位置的值
2. 該值的鍵或索引
3. 整個集合

下方範例中使用了 `pick` 的第二個參數，過濾掉鍵名首字母不是大寫字母的屬性：

許多 Underscore 的函式都可以當作迭代函式，如第一個範例使用的 `first`。除此之外，程式員還可以使用迭代縮寫以避開編寫迭代函式。下方範例中，迭代縮寫為字串 `'name'`，以從迭代物件提取鍵值為 `name` 的屬性：

「集合」類別中的所有函式，包括上方範例的 `groupBy` 和 `map` 函式，都可以遍歷迭代物件的索引和物件的鍵。下方範例使用函式 `reduce` 說明：

除了遍歷數組或物件的函式外，Underscore 還提供了廣泛的其他常用函式，例如 `throttle` 限制了目標函式的最大呼叫頻率：

另一個例子是 `defaults` 函式，僅在尚未設定時才分配物件屬性：

### `_` 函式

Underscore 的名字來源於多種用途的 `_` 函式。

#### 包裝函式

Underscore 的主函式 `_` 將第一個參數包裝起來，返回一個可以呼叫所有 Underscore 的函式的方法，此時第一個參數將作為這些函式的第一個參數傳入。此方法又被稱作「OOP 樣式」，專門用於「連結」。

可以透過 `.value()` 拿到原始傳入的值，在 JavaScript 的自動轉型下也會自己展開：

#### 部份套用佔位符

`_` 函式還可以用來當作 `partial` 函式的佔位符。`partial` 用來建立一個函式的部份套用版本，而 `_` 函式可用於使某些參數「打開（英語：open）」，而這些參數可以在後續呼叫中再傳入。 例如 § 功能概述和範例一節提到的 `groupBy` 範例可以改變成下方的用法以方便重複使用：

#### 自訂入口點

`_` 也可做為自訂 Underscore 函式的入口點，程式員可以根據需要調整 Underscore 函式的行為。具體來說，使用者可以重寫 `_.iteratee` 以建立新的迭代縮寫，又或是重寫 `_.templateSettings` 以自訂 `template` 函式。

#### 命名空間

`_` 在舊式的 AMD 模組系統和 CommonJS 模組系統中也同時作為命名空間存在，即_所有_ Underscore 函式都包含在此一命名空間中，例如 `_.map` 和 `_.debounce`。在 JavaScript 發展出 ES6 的模組系統後命名空間已無必要性。

命名空間也可以用於區分不同模組提供的函式，比如 Underscore 和 Async （頁面存檔備份，存於網際網路檔案館）都提供了名為 `each` 的函式，可以分別使用 `_.each` 和 `async.each` 來區分這些函式。

### 連結

The function `chain` can be used to create a modified version of the wrapper produced by `_` 函式. When invoked on such a _chained wrapper_, each method returns a new wrapper so that the user can continue to process intermediate results with Underscore functions:
`chain` 函式用來建立由 `_` 函式生成的包裝物件的修改版本。當在這種「鍊式包裝器（英語：chained wrapper）」上調用時，每個方法都會返回一個新的包裝物件，以便使用者可以連續使用 Underscore 函式：

也可以使用 `.value()` 結束 Underscore 函式群搭配 `return` 語法：

連結並不是 Underscore 內建的函式獨有的功能。程式員也可以傳遞自訂函式給 `mixin` 函式來為自己的函式啟用連結：

實際上 Underscore 內建的函式都是使用此方法來啟用連結，即先編寫為獨立函式，而後「混合」到 `_` 函式中。

### 迭代縮寫

如同 § 功能概述和範例所述，大多數 Underscore 的迭代函式都可使用迭代縮寫（英語：iteratee shorthand）代替取代函式。下方範例使用了前面章節的範例來演示：

實際上，這些迭代函式是將通過將簡寫值傳遞給 `_.iteratee` 來確定實際調用的函式，而 `_.iteratee` 默認為 Underscore 內建的 `iteratee` 函式，此函式根據參數值返回下列幾種函式：

#### 路徑

當傳入值是一個字串，`iteratee` 函式會將傳入值傳入 `property` 函式，此函式用於過濾鍵名與傳入值相同的鍵值。

也可以傳入陣列。當傳入陣列時，`property` 函式將遞迴搜尋目標屬性。

也可以傳入數字，傳入數字時將作為數組和字串索引。

結合上方功能，下方範例給出了計算職業名稱中第二個字元出現的次數的方法：

#### 屬性雜湊

當傳入值是一個物件，`iteratee` 函式會將傳入值傳入 `matcher` 函式，此函式會檢查鍵名與鍵值是否皆有匹配，並依照是否匹配返回 `true` 或 `false`。

#### `null` 和 `undefined`

當傳入值是 `null` 或 `undefined`，`iteratee` 函式返回一個恆等函式 `identity`。此函式用於過濾數組值在 JavaScript 中強制轉型為布林值時為 `true` 或 `false`。

#### 覆蓋 `_.iteratee`

程式員可以通過覆蓋 `_.iteratee` 來增加自訂的迭代縮寫。下方範例描述如何新增正規表達法作為迭代縮寫。

## 參考文獻

## 外部連結

* 官方網站
* Functional Javascript by Oliver Steele (internet archive; osteele.com only retains a screenshot)
* JavaScript Micro-Templating （頁面存檔備份，存於網際網路檔案館） by John Resig, the original inspiration for `_.template` （頁面存檔備份，存於網際網路檔案館）

## 參見

* Lodash
* Prototype.js
* Web 應用框架
* JavaScript 函式庫