Underscore.js

维基百科,自由的百科全书
跳转到导航 跳转到搜索
Underscore.js
File:Underscore.png
开发者Jeremy Ashkenas英语Jeremy Ashkenas和Julian Gonggrijp
首次发布2009年10月28日,​16年前​(2009-10-28[1]
当前版本1.13.4(2022年6月2日,​4年前​(2022-06-02
原始码库
  • {{URL|example.com|可选的显示文本}}
Module:EditAtWikidata第29行Lua错误:attempt to index field 'wikibase' (a nil value)
编程语言JavaScript
引擎
    Module:EditAtWikidata第29行Lua错误:attempt to index field 'wikibase' (a nil value)
    文件大小发行版 7.5 KB
    开发版 68 KB
    类型JavaScript函数库
    许可协议MIT
    网站underscorejs.org

    Underscore.js是一个捆绑常见功能的JavaScript函数库。[2]Underscore.js提供的功能类似Prototype.jsRuby,但其使用函数式编程而非基于原型编程。Underscore.js的文档将自己称为“与礼服(JQuery)和吊带(Backbone.js)搭配的领带(英语:the tie to go along with jQuery's tux, and Backbone.js' suspenders.)”。Underscore.js由Backbone.jsCoffeeScript的建立者Jeremy Ashkenas英语Jeremy Ashkenas建立。[3]

    历史[编辑]

    2009年底,为了顺利开发DocumentCloud英语DocumentCloudJeremy Ashkenas英语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.assignArray.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提供了三大功能:

    1. 100多个泛用函数集合。文档页面存档备份,存于互联网档案馆)区分出了几个类别:
      • 集合函数,例如findmapminmaxgroupByshuffle,这些函数可以对迭代物件的元素进行操作。
      • 数组函数,例如firstlastflattenchunkzip,这些函数可以对类数组物件进行操作。
      • 函数函数,例如bindmemoizepartialdebounce,这些函数将函数作为参数并返回具有改变属性的新函数(高阶函数)。
      • 物件函数为最基础的类别,包含许多也在Underscore内部使用的函数。[26]物件函数大致可以分为两个子类:
        • 类型检测函数,例如isNumberisElementisDataView
        • 物件数据函数,例如keysextendpickomitpairsinvert,这些函数将一般物件作为数据进行操作。
      • 实用函数是一个杂项类别,其中包括琐碎功能如identitynoop和字符串操作函数escapeunescapetemplate。此类别还包括函数iterateemixin,它们可以被视为第2点中提及的特殊工具。
    2. 特殊工具,例如chainiteratee,这些特殊工具与第1点的函数相结合用以实现更短、更清晰的语法。以库命名的特殊函数 _ 是这些设施的核心。

    需要阅读的有文化的原始码,以便很容易理解库的实现方式。文档包括原始码的渲染版本,其中注释在左侧,逻辑在右侧。注释使用Markdown格式化,逻辑有语法高亮。从 1.11 版本开始,Underscore 是模块化的。出于这个原因,文档现在包括带注释源的模块化版本,其中每个功能都在一个单独的页面上,并且import引用是可点击的超链接,以及一个单一阅读版本,其中所有功能都在一个页面上依赖顺序。

    1. 使用文学编程进行编程,故代码较容易阅读,且较容易理解函数库的实现方式。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会接收三个参数:

    1. 集合中当前位置的值
    2. 该值的键或索引
    3. 整个集合

    下方示例中使用了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']
    

    “集合”类别中的所有函数,包括上方示例的groupBymap函数,都可以遍历迭代对象的索引和对象的键。下方示例使用函数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]
    // }
    

    自定义入口点[编辑]

    _也可做为自定义英语personalizationUnderscore函数的入口点,程序员可以根据需要调整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的函数,可以分别使用_.eachasync.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函数,此函数会检查键名与键值是否皆有匹配,并依照是否匹配返回truefalse

    import { find } from 'underscore';
    
    find(people, {name: 'Sasha'});
    // {name: 'Sasha', details: {age: 68, occupation: 'library developer'}}
    
    find(people, {name: 'Walter'});
    // undefined
    

    nullundefined[编辑]

    当传入值是nullundefinediteratee函数返回一个恒等函数identity。此函数用于过滤数组值在JavaScript中强制转型为布尔值时为truefalse

    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']
    

    参考文献[编辑]

    1. ^ Release 0.1.0 · jashkenas/underscore. GitHub. [2022年8月25日]. (原始内容存档于2022年7月8日). 
    2. ^ Underscore.js – ein kleines Framework mit Fokus. entwickler.de. 20 June 2018 [9 July 2020]. (原始内容存档于2021-04-14) (Deutsch). 
    3. ^ JavaScript Meetup City, Open (纽约时报), April 4, 2012 [2022-08-19], (原始内容存档于2017-07-06) 
    4. ^ Ashkenas, Jeremy. Underscore 0.4.0 source. cdn.rawgit.com. [1 March 2021]. (原始内容存档于2021-03-23). 
    5. ^ Lo-Dash v2.2.1. lodash.com. [1 March 2021]. 原始内容存档于6 November 2013. 
    6. ^ Lodash Changelog - 1.0.0 rc1. github.com. 4 December 2012 [1 March 2021]. (原始内容存档于2022-08-17). 
    7. ^ Lodash Changelog - 3.0.0. github.com. 26 January 2015 [1 March 2021]. (原始内容存档于2022-08-17). 
    8. ^ Ashkenas, Jeremy. The Big Kahuna: Underscore + Lodash Merge Thread. github.com. 21 May 2015 [1 March 2021]. (原始内容存档于2022-08-17). 
    9. ^ Underscore: merged pull requests with breaking changes between 21 May and 1 October 2015. github.com. [1 March 2021]. (原始内容存档于2022-08-17). 
    10. ^ Dalton, John-David. comment on 'Core API'. github.com. 8 June 2015 [1 March 2021]. (原始内容存档于2022-08-17). 
    11. ^ Lodash changelog 4.0.0. github.com. 12 January 2016 [1 March 2021]. (原始内容存档于2022-08-17). 
    12. ^ @sailshq/lodash. npmjs.com. [1 March 2021]. (原始内容存档于2022-08-27). 
    13. ^ Dalton, John-David. Merge update.. github.com. 13 February 2016 [1 March 2021]. (原始内容存档于2020-10-12). 
    14. ^ Krebs, Adam. comment on 'Merge update.'. github.com. 17 February 2016 [1 March 2021]. (原始内容存档于2020-10-12). 
    15. ^ 15.0 15.1 jashkenas/underscore Insights: Contributors. github.com. [1 March 2021]. (原始内容存档于2022-08-17). 
    16. ^ lodash/lodash Insight: Contributors. github.com. [1 March 2021]. (原始内容存档于2022-08-17). 
    17. ^ Array.prototype.map. developer.mozilla.org. [1 March 2021]. (原始内容存档于2022-09-08). 
    18. ^ Array.prototype.filter. developer.mozilla.org. [1 March 2021]. (原始内容存档于2022-09-08). 
    19. ^ Array.prototype.forEach. developer.mozilla.org. [1 March 2021]. (原始内容存档于2022-09-08). 
    20. ^ _.map. underscorejs.org. [1 March 2021]. (原始内容存档于2022-09-09). 
    21. ^ _.filter. underscorejs.org. [1 March 2021]. (原始内容存档于2022-09-09). 
    22. ^ _.each. underscorejs.org. [1 March 2021]. (原始内容存档于2022-09-09). 
    23. ^ Underscore.js. underscorejs.org. [1 March 2021]. (原始内容存档于2022-09-09). 
    24. ^ JavaScript reference. developer.mozilla.org. [1 March 2021]. (原始内容存档于2022-09-04). 
    25. ^ underscore. npmjs.com. [1 March 2021]. (原始内容存档于2022-08-17). 
    26. ^ Gonggrijp, Julian. modules/index.js. underscorejs.org. [5 March 2021]. (原始内容存档于2022-08-17). 
    27. ^ Gonggrijp, Julian. modules/index-default.js. underscorejs.org. [4 March 2021]. (原始内容存档于2022-08-17). 

    外部链接[编辑]

    参见[编辑]