Underscore.js
- 1.13.7 (2024年7月24日;穩定版本)[2]
![編輯維基數據鏈接](http://upload.wikimedia.org/wikipedia/commons/thumb/8/8a/OOjs_UI_icon_edit-ltr-progressive.svg/10px-OOjs_UI_icon_edit-ltr-progressive.svg.png)
- github
.com /jashkenas /underscore
![編輯維基數據鏈接](http://upload.wikimedia.org/wikipedia/commons/thumb/8/8a/OOjs_UI_icon_edit-ltr-progressive.svg/10px-OOjs_UI_icon_edit-ltr-progressive.svg.png)
開發版 68 KB
Underscore.js是一個綑綁常見功能的JavaScript函數庫。[3]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(英语:Jeremy Ashkenas)建立。[4]
歷史
2009年底,為了順利開發DocumentCloud(英语:DocumentCloud),Jeremy Ashkenas(英语:Jeremy Ashkenas)開發了Underscore。 Underscore為最早提供通用函數式編程實用程序的JavaScript函數庫之一,靈感來自Prototype.js、Oliver Steele的Functional JavaScript和John Resig的Micro-Templating。[5]
2012年,John-David Dalton建立Underscore的分叉Lo-Dash(現在的Lodash)。開發初期,Lo-Dash被評價為「可客製化、效能佳和附加功能多」之Underscore的替代品。[6]儘管如此,Lodash在分叉早期階段便和Underscore的介面有不小的差異[7],甚至在3.0.0版本中開始更劇烈的變更,使得用戶必須要大量變更才能升級到新版本的Lodash或是從Underscore遷移到Lodash。[8]
2015年5月,Jeremy Ashkenas透露John-David Dalton已與他取得聯繫,希望將Lodash合併回Underscore。縱然代碼風格和代碼大小可能會對合併產生困擾,Ashkenas並不反對將Lodash的一些擴充內容合併到Underscore。[9]當時有幾個開發人員同時為Underscore和Lodash做出貢獻,這些貢獻者開始對Underscore進行更改,使其更像Lodash。[10]
然而,在眾人為此努力的同時,Dalton對Lodash的介面進行了更大幅度的更改,並於2015年6月發布Lodash的版本4.0.0。此更改使得Lodash與Underscore介面差距更大,也凸顯了其與Lodash本身的3.x系列版本地不小差異[11][12],同時此更改也促使一些依賴Lodash的項目分叉了自己的Lodash 3發行版。[13]
2016年2月,Dalton宣布他認為合併工作已經完成,並建議Underscore用戶切換到Lodash。[14]然而,Underscore的維護者明確表示,Underscore依然會作為單獨的庫存在。[15]兩個函數庫在 2016 年之後都進入了低開發活動狀態。[16][17]
隨著時間的推移,較新版本的ECMAScript標準借鑒了Underscore的部分功能,例如Object.assign
和Array.prototype.map
。儘管這些內置函數不如Underscore等效函數強大,此變更依然使得部分人認為Underscore不再為JavaScript項目增加價值。然而,新加入的數組功能只能在數組上使用,而非如同Underscore一樣可以適用任意迭代對象。[18][19][20][21][22][23]除此之外,Underscore的大部分函數仍然沒有內置對應函數。[24][25]
截至2021年3月,Julian Gonggrijp正在積極開發Underscore,他於2020年3月開始做出重大貢獻。[16]至今仍有許多函數庫依賴Underscore,npm上的每週下載次依然高達數百萬次。[26]
內容
簡而言之,Underscore提供了三大功能:
- 100多個泛用函數集合。文檔 (页面存档备份,存于互联网档案馆)區分出了幾個類別:
- 集合函數,例如
find
、map
、min
/max
、groupBy
和shuffle
,這些函數可以對迭代物件的元素進行操作。 - 數組函數,例如
first
/last
、flatten
、chunk
和zip
,這些函數可以對類數組物件進行操作。 - 函數函數,例如
bind
、memoize
、partial
和debounce
,這些函數將函數作為參數並返回具有改變屬性的新函數(高阶函数)。 - 物件函數為最基礎的類別,包含許多也在Underscore內部使用的函數。[27]物件函數大致可以分為兩個子類:
- 類型檢測函數,例如
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使用函数式编程,故而可以將多個函數混和成新的表達式(英语:expression (computer science))。例如下方的程式碼便是使用兩個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
用來建立一個函數的部份套用(英语:partial application)版本,而_
函數可用於使某些參數「打開(英語: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] // }
自定義入口點
_
也可做為自定義(英语:personalization)Underscore函數的入口點,程序員可以根據需要調整Underscore函數的行為。具體來說,用戶可以重寫_.iteratee
以建立新的迭代縮寫(英语:iteratee shorthands),又或是重寫_.templateSettings
以自定義template
函數。
命名空間
_
在舊式的AMD(英语:Asynchronous module definition)模組系統和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內建的函數都是使用此方法來啟用連結,即先編寫為獨立函數,而後「混合」到_
函數中。[28]
迭代縮寫
如同§ 功能概述和示例所述,大多數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日).
- ^ Release 1.13.7. 2024年7月24日 [2024年7月28日].
- ^ 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).
- ^ 16.0 16.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
(页面存档备份,存于互联网档案馆)
|
|