Underscore.js
開發者 | Jeremy Ashkenas和Julian Gonggrijp |
---|---|
首次发布 | 2009年10月28日[1] |
当前版本 | 1.13.4(2022年6月2日 | )
源代码库 | |
编程语言 | JavaScript |
文件大小 | 發行版 7.5 KB 開發版 68 KB |
类型 | JavaScript函式庫 |
许可协议 | MIT |
网站 | underscorejs |
Underscore.js是一個綑綁常見功能的JavaScript函數庫。[2]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建立。[3]
歷史
[编辑]2009年底,為了順利開發DocumentCloud,Jeremy Ashkenas開發了Underscore。 Underscore為最早提供通用函數式編程實用程序的JavaScript函數庫之一,靈感來自Prototype.js、Oliver Steele的Functional JavaScript和John Resig的Micro-Templating。[4]
2012年,John-David Dalton建立Underscore的分叉Lo-Dash(現在的Lodash)。開發初期,Lo-Dash被評價為「可客製化、效能佳和附加功能多」之Underscore的替代品。[5]儘管如此,Lodash在分叉早期階段便和Underscore的介面有不小的差異[6],甚至在3.0.0版本中開始更劇烈的變更,使得用戶必須要大量變更才能升級到新版本的Lodash或是從Underscore遷移到Lodash。[7]
2015年5月,Jeremy Ashkenas透露John-David Dalton已與他取得聯繫,希望將Lodash合併回Underscore。縱然代碼風格和代碼大小可能會對合併產生困擾,Ashkenas並不反對將Lodash的一些擴充內容合併到Underscore。[8]當時有幾個開發人員同時為Underscore和Lodash做出貢獻,這些貢獻者開始對Underscore進行更改,使其更像Lodash。[9]
然而,在眾人為此努力的同時,Dalton對Lodash的介面進行了更大幅度的更改,並於2015年6月發布Lodash的版本4.0.0。此更改使得Lodash與Underscore介面差距更大,也凸顯了其與Lodash本身的3.x系列版本地不小差異[10][11],同時此更改也促使一些依賴Lodash的項目分叉了自己的Lodash 3發行版。[12]
2016年2月,Dalton宣布他認為合併工作已經完成,並建議Underscore用戶切換到Lodash。[13]然而,Underscore的維護者明確表示,Underscore依然會作為單獨的庫存在。[14]兩個函數庫在 2016 年之後都進入了低開發活動狀態。[15][16]
隨著時間的推移,較新版本的ECMAScript標準借鑒了Underscore的部分功能,例如Object.assign
和Array.prototype.map
。儘管這些內置函數不如Underscore等效函數強大,此變更依然使得部分人認為Underscore不再為JavaScript項目增加價值。然而,新加入的數組功能只能在數組上使用,而非如同Underscore一樣可以適用任意迭代對象。[17][18][19][20][21][22]除此之外,Underscore的大部分函數仍然沒有內置對應函數。[23][24]
截至2021年3月,Julian Gonggrijp正在積極開發Underscore,他於2020年3月開始做出重大貢獻。[15]至今仍有許多函數庫依賴Underscore,npm上的每週下載次依然高達數百萬次。[25]
內容
[编辑]簡而言之,Underscore提供了三大功能:
- 100多個泛用函數集合。文檔 (页面存档备份,存于互联网档案馆)區分出了幾個類別:
- 集合函數,例如
find
、map
、min
/max
、groupBy
和shuffle
,這些函數可以對迭代物件的元素進行操作。 - 數組函數,例如
first
/last
、flatten
、chunk
和zip
,這些函數可以對類數組物件進行操作。 - 函數函數,例如
bind
、memoize
、partial
和debounce
,這些函數將函數作為參數並返回具有改變屬性的新函數(高阶函数)。 - 物件函數為最基礎的類別,包含許多也在Underscore內部使用的函數。[26]物件函數大致可以分為兩個子類:
- 類型檢測函數,例如
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進行了模組化,因此文檔也同步加入了模組化版本 (页面存档备份,存于互联网档案馆),每個功能都在一個單獨的頁面上,並且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會接收三個參數:
- 集合中當前位置的值
- 該值的鍵或索引
- 整個集合
下方示例中使用了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
用來建立一個函數的部份套用版本,而_
函數可用於使某些參數「打開(英語: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 (页面存档备份,存于互联网档案馆)都提供了名為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內建的函數都是使用此方法來啟用連結,即先編寫為獨立函數,而後「混合」到_
函數中。[27]
迭代縮寫
[编辑]如同§ 功能概述和示例所述,大多數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']
參考文獻
[编辑]- ^ Release 0.1.0 · jashkenas/underscore. GitHub. [2022年8月25日]. (原始内容存档于2022年7月8日).
- ^ Underscore.js – ein kleines Framework mit Fokus. entwickler.de. 20 June 2018 [9 July 2020]. (原始内容存档于2021-04-14) (德语).
- ^ JavaScript Meetup City, Open (纽约时报), April 4, 2012 [2022-08-19], (原始内容存档于2017-07-06)
- ^ Ashkenas, Jeremy. Underscore 0.4.0 source. cdn.rawgit.com. [1 March 2021]. (原始内容存档于2021-03-23).
- ^ Lo-Dash v2.2.1. lodash.com. [1 March 2021]. 原始内容存档于6 November 2013.
- ^ Lodash Changelog - 1.0.0 rc1. github.com. 4 December 2012 [1 March 2021]. (原始内容存档于2022-08-17).
- ^ Lodash Changelog - 3.0.0. github.com. 26 January 2015 [1 March 2021]. (原始内容存档于2022-08-17).
- ^ Ashkenas, Jeremy. The Big Kahuna: Underscore Lodash Merge Thread. github.com. 21 May 2015 [1 March 2021]. (原始内容存档于2022-08-17).
- ^ Underscore: merged pull requests with breaking changes between 21 May and 1 October 2015. github.com. [1 March 2021]. (原始内容存档于2022-08-17).
- ^ Dalton, John-David. comment on 'Core API'. github.com. 8 June 2015 [1 March 2021]. (原始内容存档于2022-08-17).
- ^ Lodash changelog 4.0.0. github.com. 12 January 2016 [1 March 2021]. (原始内容存档于2022-08-17).
- ^ @sailshq/lodash. npmjs.com. [1 March 2021]. (原始内容存档于2022-08-27).
- ^ Dalton, John-David. Merge update.. github.com. 13 February 2016 [1 March 2021]. (原始内容存档于2020-10-12).
- ^ Krebs, Adam. comment on 'Merge update.'. github.com. 17 February 2016 [1 March 2021]. (原始内容存档于2020-10-12).
- ^ 15.0 15.1 jashkenas/underscore Insights: Contributors. github.com. [1 March 2021]. (原始内容存档于2022-08-17).
- ^ lodash/lodash Insight: Contributors. github.com. [1 March 2021]. (原始内容存档于2022-08-17).
- ^ Array.prototype.map. developer.mozilla.org. [1 March 2021]. (原始内容存档于2022-09-08).
- ^ Array.prototype.filter. developer.mozilla.org. [1 March 2021]. (原始内容存档于2022-09-08).
- ^ Array.prototype.forEach. developer.mozilla.org. [1 March 2021]. (原始内容存档于2022-09-08).
- ^ _.map. underscorejs.org. [1 March 2021]. (原始内容存档于2022-09-09).
- ^ _.filter. underscorejs.org. [1 March 2021]. (原始内容存档于2022-09-09).
- ^ _.each. underscorejs.org. [1 March 2021]. (原始内容存档于2022-09-09).
- ^ Underscore.js. underscorejs.org. [1 March 2021]. (原始内容存档于2022-09-09).
- ^ JavaScript reference. developer.mozilla.org. [1 March 2021]. (原始内容存档于2022-09-04).
- ^ underscore. npmjs.com. [1 March 2021]. (原始内容存档于2022-08-17).
- ^ Gonggrijp, Julian. modules/index.js. underscorejs.org. [5 March 2021]. (原始内容存档于2022-08-17).
- ^ Gonggrijp, Julian. modules/index-default.js. 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
(页面存档备份,存于互联网档案馆)