【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建立。
== 历史 ==
2009年底，为了顺利开发DocumentCloud，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多个泛用函数集合。 文档 date=20220909023026 区分出了几个类别：
#* 集合函数，例如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进行了模组化，因此文档也同步加入了 模组化版本 date=20220817153502 ，每个功能都在一个单独的页面上，并且import引用是可点击的超连结；同时也文档也提供了一个 汇集所有模组的版本 date=20220817153322 ，使用拓扑排序进行排序。
=== 功能概述和示例 ===
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 => array0;
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) => key0 === key0.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样式」，专门用于「next section」。
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 date=20220616051257 都提供了名为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 ====
当传入值是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'
== 参考文献 ==
== 外部连结 ==
* Functional Javascript by Oliver Steele (internet archive; osteele.com only retains a  screenshot)
* JavaScript Micro-Templating date=20220810035513  by John Resig, the original inspiration for  _.template date=20220909023026