**Underscore.js**是一個綑綁常見功能的JavaScript函數庫。Underscore.js提供的功能類似Prototype.js和Ruby，但其使用函数式编程而非基于原型编程。Underscore.js的文檔將自己稱為「與禮服（JQuery）和吊帶（Backbone.js）搭配的領帶（英語：）」。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函數以使用第一個字元進行分組：

```
import { groupBy, first } from 'underscore';

groupBy(['avocado', 'apricot', 'cherry', 'date', 'durian'], first);

// result:
// { a: ['avocado', 'apricot'],
// c: ['cherry'],
// d: ['date', 'durian']
// }

```

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

```
import { groupBy } from 'underscore';

const first = array => array[0];

groupBy(['avocado', 'apricot', 'cherry', 'date', 'durian'], first);

```

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

正常情況下，第一個參數會傳入迭代對象，第二個參數傳入迭代函數或*iteratee*。上方示例中，`first`是傳遞給`groupBy`的迭代函數。

iteratee會接收三個參數：

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

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

```
import { pick } from 'underscore';

const details = {
 Official: 'Wolfgang Amadeus Mozart',
 informal: 'Wolfie'
};
const keyIsUpper = (value, key) => key[0] === key[0].toUpperCase();

pick(details, keyIsUpper);
// {Official: 'Wolfgang Amadeus Mozart'}

```

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

```
import { map } from 'underscore';

const people = [
 {name: 'Lily', age: 44, occupation: 'librarian'},
 {name: 'Harold', age: 10, occupation: 'dreamer'},
 {name: 'Sasha', age: 68, occupation: 'library developer'}
];

map(people, 'name'); // ['Lily', 'Harold', 'Sasha']

```

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

```
import { reduce } from 'underscore';

const add = (a, b) => a + b;
const sum = numbers => reduce(numbers, add, 0);

sum([11, 12, 13]); // 36
sum({Alice: 9, Bob: 9, Clair: 7}); // 25

```

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

```
import { throttle } from 'underscore';

// The scroll event triggers very often, so the following line may
// slow down the browser.
document.body.addEventListener('scroll', expensiveUpdateFunction);

// Limit evaluation to once every 100 milliseconds.
const throttledUpdateFunction = throttle(expensiveUpdateFunction, 100);

// Much smoother user experience!
document.body.addEventListener('scroll', throttledUpdateFunction);

```

另一個例子是`defaults`函數，僅在尚未設置時才分配對象屬性：

```
import { defaults } from 'underscore';

const requestData = {
 url: 'wikipedia.org',
 method: 'POST',
 body: 'article text'
};
const defaultFields = {
 method: 'GET',
 headers: {'X-Requested-With': 'XMLHttpRequest'}
};

defaults(requestData, defaultFields);
// {
// url: 'wikipedia.org',
// method: 'POST',
// body: 'article text',
// headers: {'X-Requested-With': 'XMLHttpRequest'}
// }

```

### `_`函數

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

#### 包裝函數

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

```
import \_, { last } from 'underscore';

// "Normal" or "functional" style
last([1, 2, 3]); // 3

// "OOP style"
\_([1, 2, 3]).last() // 3

```

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

```
// Explicit unwrap
\_([1, 2, 3]).value() // [1, 2, 3]

// Automatic unwrap when coerced to number
1 + \_(2) // 3

// Automatic unwrap when coerced to string
'abc' + \_('def') // 'abcdef'

// Automatic unwrap when formatted as JSON
JSON.stringify({ a: \_([1, 2]) }) // '{"a":[1,2]}'

```

#### 部份套用佔位符

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

```
import \_, { partial, groupBy, first } from 'underscore';

const groupByFirstChar = partial(groupBy, \_, first);

groupByFirstChar(['avocado', 'apricot', 'cherry', 'date', 'durian']);
// { a: ['avocado', 'apricot'],
// c: ['cherry'],
// d: ['date', 'durian']
// }

groupByFirstChar(['chestnut', 'pistache', 'walnut', 'cashew']);
// { c: ['chestnut', 'cashew'],
// p: ['pistache'],
// w: ['walnut]
// }

```

#### 自定義入口點

`_`也可做為自定義（英语：）Underscore函數的入口點，程序員可以根據需要調整Underscore函數的行為。具體來說，用戶可以重寫`_.iteratee`以建立新的迭代縮寫（英语：），又或是重寫`_.templateSettings`以自定義`template`函數。

#### 命名空間

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

```
var \_ = require('underscore');

\_.groupBy(['avocado', 'apricot', 'cherry', 'date', 'durian'], \_.first);

```

命名空間也可以用於區分不同模組提供的函數，比如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`函數用來建立由`_`函數生成的包裝物件的修改版本。當在這種「鍊式包裝器（英語：）」上調用時，每個方法都會返回一個新的包裝物件，以便用戶可以連續使用Underscore函數：

```
import { chain } from 'underscore';

const square = x => x \* x;
const isOdd = x => x % 2;

chain([1, 2, 3, 4]).filter(isOdd).map(square).last()
// returns a wrapper of 9

```

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

```
const add = (x, y) => x + y;

// Given an array of numbers, return the sum of the squares of
// those numbers. This could be used in a statistics library.
function sumOfSquares(numbers) {
 return chain(numbers)
 .map(square)
 .reduce(add)
 .value();
}

```

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

```
import { reduce, mixin } from 'underscore';

const sum = numbers => reduce(numbers, add, 0);

mixin({ sum, square });

chain([1, 2, 3]).map(square).sum().value(); // 14
chain([1, 2, 3]).sum().square().value(); // 36

```

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

### 迭代縮寫

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

```
import { map } from 'underscore';

const people = [
 {name: 'Lily', details: {age: 44, occupation: 'fire fighter'}},
 {name: 'Harold', details: {age: 10, occupation: 'dreamer'}},
 {name: 'Sasha', details: {age: 68, occupation: 'library developer'}}
];

map(people, 'name'); // ['Lily', 'Harold', 'Sasha']

```

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

#### 路徑

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

```
import { iteratee, property } from 'underscore';

map(people, 'name');
map(people, iteratee('name'));
map(people, property('name'));
map(people, obj => obj && obj['name']);
// ['Lily', 'Harold', 'Sasha']

```

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

```
map(people, ['details', 'occupation']);
// ['fire fighter', 'dreamer', 'library developer']

```

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

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

```
import { countBy } from 'underscore';

countBy(people, ['details', 'occupation', 1]); // {i: 2, r: 1}

```

#### 屬性雜湊

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

```
import { find } from 'underscore';

find(people, {name: 'Sasha'});
// {name: 'Sasha', details: {age: 68, occupation: 'library developer'}}

find(people, {name: 'Walter'});
// undefined

```

#### `null`和`undefined`

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

```
import { filter, iteratee, identity } from 'underscore';

const example = [0, 1, '', 'abc', true, false, {}];

// The following expressions are all equivalent.
filter(example);
filter(example, undefined);
filter(example, iteratee(undefined));
filter(example, identity);
// [1, 'abc', true, {}]

```

#### 覆蓋`_.iteratee`

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

```
import {
 iteratee as originalIteratee,
 isRegExp,
 mixin,
 filter,
} from 'underscore';

function iteratee(value, context) {
 if (isRegExp(value)) {
 return string => value.test(string);
 } else {
 return originalIteratee(value, context);
 }
}

mixin({iteratee});

filter(['absolutely', 'amazing', 'fabulous', 'trousers'], /ab/);
// ['absolutely', 'fabulous']

```

參考文獻
----

1. ↑ . GitHub.  [2022年8月25日]. （原始内容存档于2022年7月8日）.
2. ↑ . 2022年9月23日  [2022年10月5日].
3. ↑  ; 检索日期: 2023年3月3日.
4. ↑ . entwickler.de. 20 June 2018  [9 July 2020]. （原始内容存档于2021-04-14） （德语）.
5. ↑  , Open (纽约时报), April 4, 2012  [2022-08-19], （原始内容存档于2017-07-06）
6. ↑  Ashkenas, Jeremy.. cdn.rawgit.com.  [1 March 2021]. （原始内容存档于2021-03-23）.
7. ↑ . lodash.com.  [1 March 2021]. 原始内容存档于6 November 2013.
8. ↑ . github.com. 4 December 2012  [1 March 2021]. （原始内容存档于2022-08-17）.
9. ↑ . github.com. 26 January 2015  [1 March 2021]. （原始内容存档于2022-08-17）.
10. ↑  Ashkenas, Jeremy.. github.com. 21 May 2015  [1 March 2021]. （原始内容存档于2022-08-17）.
11. ↑ . github.com.  [1 March 2021]. （原始内容存档于2022-08-17）.
12. ↑  Dalton, John-David.. github.com. 8 June 2015  [1 March 2021]. （原始内容存档于2022-08-17）.
13. ↑ . github.com. 12 January 2016  [1 March 2021]. （原始内容存档于2022-08-17）.
14. ↑ . npmjs.com.  [1 March 2021]. （原始内容存档于2022-08-27）.
15. ↑  Dalton, John-David.. github.com. 13 February 2016  [1 March 2021]. （原始内容存档于2020-10-12）.
16. ↑  Krebs, Adam.. github.com. 17 February 2016  [1 March 2021]. （原始内容存档于2020-10-12）.
17. 1 2 . github.com.  [1 March 2021]. （原始内容存档于2022-08-17）.
18. ↑ . github.com.  [1 March 2021]. （原始内容存档于2022-08-17）.
19. ↑ . developer.mozilla.org.  [1 March 2021]. （原始内容存档于2022-09-08）.
20. ↑ . developer.mozilla.org.  [1 March 2021]. （原始内容存档于2022-09-08）.
21. ↑ . developer.mozilla.org.  [1 March 2021]. （原始内容存档于2022-09-08）.
22. ↑ . underscorejs.org.  [1 March 2021]. （原始内容存档于2022-09-09）.
23. ↑ . underscorejs.org.  [1 March 2021]. （原始内容存档于2022-09-09）.
24. ↑ . underscorejs.org.  [1 March 2021]. （原始内容存档于2022-09-09）.
25. ↑ . underscorejs.org.  [1 March 2021]. （原始内容存档于2022-09-09）.
26. ↑ . developer.mozilla.org.  [1 March 2021]. （原始内容存档于2022-09-04）.
27. ↑ . npmjs.com.  [1 March 2021]. （原始内容存档于2022-08-17）.
28. ↑  Gonggrijp, Julian.. underscorejs.org.  [5 March 2021]. （原始内容存档于2022-08-17）.
29. ↑  Gonggrijp, Julian.. underscorejs.org.  [4 March 2021]. （原始内容存档于2022-08-17）.

外部連結
----

* 官方网站
* Functional Javascript by Oliver Steele (internet archive; osteele.com only retains a screenshot[])
* JavaScript Micro-Templating （页面存档备份，存于） by John Resig, the original inspiration for `_.template` （页面存档备份，存于）

|  |  |  |  |  |  |  |  |
| --- | --- | --- | --- | --- | --- | --- | --- |
| 

| * 
* 论
* 编
JavaScript模板（英语：） |
| --- |
|  |
| 函式庫 | 
* AngularJS
* Backbone.js
* Ember.js
* Handlebars.js（英语：）
* KnockoutJS（英语：）
* Mustache.js（英语：）
* React.js
* Vue.js
* Svelte

 |
|  |
| 架構模式 | 
* Model–view–controller (MVC)
* Model–view–viewmodel (MVVM)

 |

 |

|  |  |  |  |  |  |  |  |  |  |  |  |  |  |  |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| 

| * 
* 论
* 编
Node.js |
| --- |
|  |
| 平台 | 
* Node.js
* NPM
* V8
* CommonJS

 |  |
|  |
| 框架 | 
* MEAN
	+ MongoDB
	+ Express.js
	+ AngularJS/Angular
* MEEN（被Ember.js取代）
* Backbone.js
* Meteor（英语：）
* Sails.js（英语：）（基於Express.js）
* Next.js

 |
|  |
| 函式庫 | 
* Lodash
* Underscore.js
* React.js
* Vue.js

 |
|  |
| 程式語言 | 
* JavaScript
* CoffeeScript
* TypeScript

 |

 |

參見
--

* 自由软件主题

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