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（英语：Jeremy Ashkenas）建立。 2009年底，為了順利開發DocumentCloud（英语：DocumentCloud），Jeremy Ashkenas（英语：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多個泛用函數集合。文檔 （页面存档备份，存于互联网档案馆）區分出了幾個類別： 集合函數，例如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進行了模組化，因此文檔也同步加入了模組化版本 （页面存档备份，存于互联网档案馆），每個功能都在一個單獨的頁面上，並且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內建的函數都是使用此方法來啟用連結，即先編寫為獨立函數，而後「混合」到_函數中。 如同§ 功能概述和示例所述，大多數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，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來增加自定義的迭代縮寫。下方範例描述如何新增正規表達法作為迭代縮寫。 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）.  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 （页面存档备份，存于互联网档案馆） 自由软件主题 Lodash Prototype.js Web应用框架 JavaScript函式庫