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提供了三大功能：
# 100多個泛用函數集合。 文檔 date=20220909023026 區分出了幾個類別：
#* 集合函數，例如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點中提及的特殊工具。
# 特殊工具，例如chain和iteratee，這些特殊工具與第1點的函數相結合用以實現更短、更清晰的語法。以庫命名的特殊函數 _ 是這些設施的核心。
需要閱讀的有文化的源代碼，以便很容易理解庫的實現方式。文檔包括源代碼的渲染版本，其中註釋在左側，邏輯在右側。註釋使用Markdown格式化，邏輯有語法高亮。從 1.11 版本開始，Underscore 是模塊化的。出於這個原因，文檔現在包括帶註釋源的模塊化版本，其中每個功能都在一個單獨的頁面上，並且import引用是可點擊的超鏈接，以及一個單一閱讀版本，其中所有功能都在一個頁面上依賴順序。
# 使用文學編程進行編程，故程式碼較容易閱讀，且較容易理解函數庫的實現方式。Underscore文檔加入了源始碼的渲染版本，其中註釋在左側，邏輯在右側。註釋使用Markdown格式化，邏輯加上了語法突顯。自1.11版起，Underscore進行了模組化，因此文檔也同步加入了 模組化版本 date=20220817153502 ，每個功能都在一個單獨的頁面上，並且import引用是可點擊的超連結；同時也文檔也提供了一個 匯集所有模組的版本 date=20220817153322 ，使用拓撲排序進行排序。

**功能概述和示例**
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 => array0;
groupBy('avocado', 'apricot', 'cherry', 'date', 'durian', first);
Underscore內建不少這類型的函數，以便程序員可以從現有函數組合功能，而非每次都要自己實踐。
正常情況下，第一個參數會傳入迭代對象，第二個參數傳入迭代函數或iteratee。上方示例中，first是傳遞給groupBy的迭代函數。
iteratee會接收三個參數：
# 集合中當前位置的值
# 該值的鍵或索引
# 整個集合
下方示例中使用了pick的第二個參數，過濾掉鍵名首字母不是大寫字母的屬性：
import { pick } from 'underscore';
const details = {
Official: 'Wolfgang Amadeus Mozart',
informal: 'Wolfie'
};
const keyIsUpper = (value, key) => key0 === key0.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樣式」，專門用於「next section」。
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用來建立一個函數的部份套用版本，而_函數可用於使某些參數「打開（open）」，而這些參數可以在後續呼叫中再傳入。 例如功能概述和示例一節提到的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 date=20220616051257 都提供了名為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函數：
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的迭代函數都可使用迭代縮寫（iteratee shorthand）代替取代函數。下方範例使用了前面章節的範例來演示：
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'

**參考文獻**

**外部連結**
 Functional Javascript by Oliver Steele (internet archive; osteele.com only retains a  screenshot)- JavaScript Micro-Templating date=20220810035513  by John Resig, the original inspiration for  _.template date=20220909023026 

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