Underscore.js

Underscore.js是一個綑綁常見功能的JavaScript函式庫。Underscore.js提供的功能類似Prototype.js和Ruby，但其使用函數語言程式設計而非基於原型程式設計。Underscore.js的文件將自己稱為「與禮服」。Underscore.js由Backbone.js和CoffeeScript的建立者建立。歷史.2009年底，為了順利開發，開發了Underscore。Underscore為最早提供通用函數語言程式設計實用程式的JavaScript函式庫之一，靈感來自Prototype.js、Oliver Steele的Functional JavaScript和John Resig的Micro-Templating。2012年，John-David Dalton建立Underscore的分叉Lo-Dash。開發初期，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的部分功能，例如codice_1和codice_2。儘管這些內建函式不如Underscore等效函式強大，此變更依然使得部分人認為Underscore不再為JavaScript專案增加價值。然而，新加入的陣列功能只能在陣列上使用，而非如同Underscore一樣可以適用任意迭代物件。除此之外，Underscore的大部分函式仍然沒有內建對應函式。截至2021年3月，Julian Gonggrijp正在積極開發Underscore，他於2020年3月開始做出重大貢獻。至今仍有許多函式庫依賴Underscore，npm上的每週下載次依然高達數百萬次。內容.簡而言之，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內建的函式，也可以自訂函式。例如下方程式碼實踐了自己的codice_9函式，其結果和上方程式碼相同：import { groupBy } from 'underscore';const first = array =&gt; array[0];groupBy(['avocado', 'apricot', 'cherry', 'date', 'durian'], first);Underscore內建不少這型別的函式，以便程式設計師可以從現有函式組合功能，而非每次都要自己實踐。正常情況下，第一個引數會傳入迭代物件，第二個引數傳入迭代函式或"iteratee"。上方示例中，codice_9是傳遞給codice_7的迭代函式。iteratee會接收三個引數：下方示例中使用了codice_23的第二個引數，過濾掉鍵名首字母不是大寫字母的屬性：import { pick } from 'underscore';const details = {Official: 'Wolfgang Amadeus Mozart',informal: 'Wolfie'const keyIsUpper = (value, key) =&gt; key[0] === key[0].toUpperCase();pick(details, keyIsUpper);許多Underscore的函式都可以當作迭代函式，如第一個範例使用的codice_9。除此之外，程式設計師還可以使用迭代縮寫以避開編寫迭代函式。下方示例中，迭代縮寫為字串codice_42，以從迭代物件提取鍵值為codice_43的屬性：import { map } from 'underscore';const people = [{name: 'Lily', age: 44, occupation: 'librarian'},{name: 'Harold', age: 10, occupation: 'dreamer'},map(people, 'name'); // ['Lily', 'Harold', 'Sasha']「集合」類別中的所有函式，包括上方範例的codice_7和codice_4函式，都可以遍歷迭代物件的索引和物件的鍵。下方示例使用函式codice_46說明：import { reduce } from 'underscore';const add = (a, b) =&gt; a + b;const sum = numbers =&gt; reduce(numbers, add, 0);sum([11, 12, 13]); // 36sum({Alice: 9, Bob: 9, Clair: 7}); // 25除了遍歷陣列或物件的函式外，Underscore還提供了廣泛的其他常用函式，例如codice_47限制了目標函式的最大呼叫頻率：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);另一個例子是codice_48函式，僅在尚未設定時才分配物件屬性：import { defaults } from 'underscore';const requestData = {url: 'wikipedia.org',method: 'POST',body: 'article text'const defaultFields = {method: 'GET',defaults(requestData, defaultFields);// url: 'wikipedia.org',// method: 'POST',// body: 'article text',codice_36函式.Underscore的名字多種用途的codice_36函式。包裝函式.Underscore的主函式codice_36將第一個引數包裝起來，返回一個可以呼叫所有Underscore的函式的方法，此時第一個引數將作為這些函式的第一個引數傳入。此方法又被稱作「OOP樣式」，專門用於「」。import _, { last } from 'underscore';// "Normal" or "functional" stylelast([1, 2, 3]); // 3// "OOP style"_([1, 2, 3]).last() // 3可以透過codice_52拿到原始傳入的值，在JavaScript的自動轉型下也會自己展開：// Explicit unwrap_([1, 2, 3]).value() // [1, 2, 3]// Automatic unwrap when coerced to number1 + _(2) // 3// Automatic unwrap when coerced to string'abc' + _('def') // 'abcdef'// Automatic unwrap when formatted as JSONJSON.stringify({ a: _([1, 2]) }) // '{"a":[1,2]}'部份套用佔位符.codice_36函式還可以用來當作codice_16函式的佔位符。codice_16用來建立一個函式的版本，而codice_36函式可用於使某些引數「開啟」，而這些引數可以在後續呼叫中再傳入。 例如#重定向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]自定義入口點.codice_36也可做為Underscore函式的入口點，程式設計師可以根據需要調整Underscore函式的行為。具體來說，使用者可以重寫codice_59以建立新的，又或是重寫codice_60以自定義codice_31函式。名稱空間.codice_36在舊式的模組系統和CommonJS模組系統中也同時作為名稱空間存在，即"所有"Underscore函式都包含在此一名稱空間中，例如codice_63和codice_64。在JavaScript發展出ES6的模組系統後名稱空間已無必要性。var _ = require('underscore');_.groupBy(['avocado', 'apricot', 'cherry', 'date', 'durian'], _.first);名稱空間也可以用於區分不同模組提供的函式，比如Underscore和Async 都提供了名為codice_65的函式，可以分別使用codice_66和codice_67來區分這些函式。連結.The function codice_34 can be used to create a modified version of the wrapper produced by codice_36函式. 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:codice_34函式用來建立由codice_36函式生成的包裝物件的修改版本。當在這種「鍊式包裝器」上呼叫時，每個方法都會返回一個新的包裝物件，以便使用者可以連續使用Underscore函式：import { chain } from 'underscore';const square = x =&gt; x * x;const isOdd = x =&gt; x % 2;chain([1, 2, 3, 4]).filter(isOdd).map(square).last()// returns a wrapper of 9也可以使用codice_52結束Underscore函式群搭配codice_73語法：const add = (x, y) =&gt; 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內建的函式獨有的功能。程式設計師也可以傳遞自定義函式給codice_33函式來為自己的函式啟用連結：import { reduce, mixin } from 'underscore';const sum = numbers =&gt; reduce(numbers, add, 0);mixin({ sum, square });chain([1, 2, 3]).map(square).sum().value(); // 14chain([1, 2, 3]).sum().square().value(); // 36實際上Underscore內建的函式都是使用此方法來啟用連結，即先編寫為獨立函式，而後「混合」到codice_36函式中。迭代縮寫.如同#重定向import { map } from 'underscore';const people = [{name: 'Lily', details: {age: 44, occupation: 'fire fighter'}},{name: 'Harold', details: {age: 10, occupation: 'dreamer'}},map(people, 'name'); // ['Lily', 'Harold', 'Sasha']實際上，這些迭代函式是將透過將簡寫值傳遞給codice_59來確定實際呼叫的函式，而codice_59預設為Underscore內建的codice_32函式，此函式根據引數值返回下列幾種函式：路徑.當傳入值是一個字串，codice_32函式會將傳入值傳入codice_80函式，此函式用於過濾鍵名與傳入值相同的鍵值。import { iteratee, property } from 'underscore';map(people, 'name');map(people, iteratee('name'));map(people, property('name'));map(people, obj =&gt; obj &amp;&amp; obj['name']);// ['Lily', 'Harold', 'Sasha']也可以傳入陣列。當傳入陣列時，codice_80函式將遞迴搜尋目標屬性。map(people, ['details', 'occupation']);// ['fire fighter', 'dreamer', 'library developer']也可以傳入數字，傳入數字時將作為陣列和字串索引。結合上方功能，下方範例給出了計算職業名稱中第二個字元出現的次數的方法：import { countBy } from 'underscore';屬性雜湊.當傳入值是一個物件，codice_32函式會將傳入值傳入codice_83函式，此函式會檢查鍵名與鍵值是否皆有匹配，並依照是否匹配返回codice_84或codice_85。import { find } from 'underscore';find(people, {name: 'Sasha'});find(people, {name: 'Walter'});// undefinedcodice_86和codice_87.當傳入值是codice_86或codice_87，codice_32函式返回一個恆等函式codice_27。此函式用於過濾陣列值在JavaScript中強制轉型為布林值時為codice_84或codice_85。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, {}]覆蓋codice_59.程式設計師可以透過覆蓋codice_59來增加自定義的迭代縮寫。下方範例描述如何新增正規表達法作為迭代縮寫。import {iteratee as originalIteratee,isRegExp,mixin,filter,} from 'underscore';function iteratee(value, context) {if (isRegExp(value)) {return string =&gt; value.test(string);} else {return originalIteratee(value, context);mixin({iteratee});filter(['absolutely', 'amazing', 'fabulous', 'trousers'], /ab/);// ['absolutely', 'fabulous']