underscore.js源码加解释一共1500多行,它供应了一整套函数式编程有用的功用,一共一百多个函数,险些每一个函数都能够作为参考模范。初读的时刻,真是一脸懵圈,种种函数闭包、迭代和嵌套的运用,让我一时很难消化。
在这里,我来纪录一下我进修underscore.js的一些发明,以及几个我以为比较典范的函数运用。
起首我们能够看到,underscore.js中一切的函数和要领都在一个闭包里:(function() {...}.call(this));
这么做的目标是为了防备污染全局变量。
为了紧缩代码,underscore中用到将原型赋值给变量保留的要领:
// Save bytes in the minified (but not gzipped) version:
// 原型赋值,便于紧缩代码,这里的紧缩指紧缩到min.js而不是gzip紧缩
var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
// Create quick reference variables for speed access to core prototypes.
// 将内置对象原型中的经常使用要领赋值给援用变量,削减在原型链中的查找次数,从而进步代码效力
var
push = ArrayProto.push,
slice = ArrayProto.slice,
toString = ObjProto.toString,
hasOwnProperty = ObjProto.hasOwnProperty;
我们在处置惩罚代码时,不能直接将Array.prototype等直接紧缩,由于紧缩事后,浏览器是没法辨认这些紧缩字段的。紧缩事后,我们在运用obj.prototype要领时,直接运用其相对应的变量就能够了。假如在我们的代码中会屡次用到某个要领,用上面的要领就举行处置惩罚,运用起来就轻易多了。
接着建立了一个”_”对象,今后将underscore中的相干要领添加到”_”原型中,那末建立的”_”对象也就具有了underscore要领。
// 建立一个"_"对象
var _ = function(obj) {
if (obj instanceof _) return obj; //假如obj是"—"的实例,则直接返回obj
if (!(this instanceof _)) return new _(obj); //假如不是,则挪用new运算符,返回实例化的对象
this._wrapped = obj; //将underscore对象存放在_.wrapped属性中
};
上面用到了instanceof运算符,JavaScript中instanceof运算符是返回一个 Boolean 值,指出对象是不是是特定类的一个实例。
运用要领:result = object instanceof class
个中,result是必选项,表恣意变量;object是必选项,表恣意对象表达式;class是必选项,表恣意已定义的对象类。假如 object 是 class 的一个实例,则 instanceof 运算符返回 true。假如 object 不是指定类的一个实例,或许 object 是 null,则返回 false。
接下来我就枚举几个underscore中的函数。
1、_.each
_.each = _.forEach = function(obj, iteratee, context) {
iteratee = optimizeCb(iteratee, context); // 依据 context 肯定差别的迭代函数
var i, length;
if (isArrayLike(obj)) { // 假如是类数组 (默许不会传入相似 {length: 10} 如许的数据)
for (i = 0, length = obj.length; i < length; i++) { //遍历
iteratee(obj[i], i, obj);
}
} else { // 假如 obj 是对象
var keys = _.keys(obj); // 猎取对象的一切 key 值
for (i = 0, length = keys.length; i < length; i++) { //假如是对象,则遍历处置惩罚 values 值
iteratee(obj[keys[i]], keys[i], obj);
}
}
return obj; //返回 obj 参数,供链式挪用(Returns the list for chaining)
};
_.each = _.forEach = function(obj, iteratee, context)中,一共有三个参数:
第一个参数为数组(包括类数组)或许对象;第二个参数为迭代要领,对数组或许对象每一个元素都实行
该要领,该要领又能传入三个参数,分别为 (item, index, array)((value, key, obj) for object);
第三个参数(可省略)肯定第二个参数 iteratee 函数中的(能够有的)this 指向,
即 iteratee 中涌现的(假如有)一切 this 都指向 context。
2、_.contains
_.contains = _.includes = _.include = function(obj, item, fromIndex, guard) {
if (!isArrayLike(obj)) obj = _.values(obj); // // 假如是对象,返回 values 构成的数组
//fromIndex 示意查询肇端位置,假如没有指定该参数,则默许重新找起
if (typeof fromIndex != 'number' || guard) fromIndex = 0;
//_.indexOf 是数组的扩大要领(Array Functions)
//数组中寻觅某一元素
return _.indexOf(obj, item, fromIndex) >= 0;
};
推断数组或许对象中(value 值)是不是有指定元素,假如是 object,则疏忽 key 值,只须要查找 value 值即可,假如obj 中是不是有指定的 value 值,则返回布尔值。
3、 _.uniq
_.uniq = _.unique = function(array, isSorted, iteratee, context) {
if (!_.isBoolean(isSorted)) { // 没有传入 isSorted 参数
context = iteratee;
iteratee = isSorted;
isSorted = false; // 转为 _.unique(array, false, undefined, iteratee)
}
// 假如有迭代函数,则依据 this 指向二次返回新的迭代函数
if (iteratee != null) iteratee = cb(iteratee, context);
var result = []; // 效果数组,是 array 的子集
var seen = []; //// 已涌现过的元素(或许经由迭代过的值),用来过滤反复值
for (var i = 0, length = getLength(array); i < length; i++) {
var value = array[i],
//假如指定了迭代函数,则对数组每一个元素举行迭代
//迭代函数传入的三个参数通常是 value, index, array 情势
computed = iteratee ? iteratee(value, i, array) : value;
//假如是有序数组,则当前元素只需跟上一个元素对照即可,并用 seen 变量保留上一个元素
if (isSorted) {
//假如 i === 0,是第一个元素,则直接 push,不然比较当前元素是不是和前一个元素相称
if (!i || seen !== computed) result.push(value);
seen = computed; // seen 保留当前元素,供下一次对照
} else if (iteratee) {
if (!_.contains(seen, computed)) { //// 假如 seen[] 中没有 computed 这个元素值
seen.push(computed);
result.push(value);
}
} else if (!_.contains(result, value)) {
// 假如不必经由迭代函数盘算,也就不必 seen[] 变量了
result.push(value);
}
}
return result;
};
这是一个数组去重函数,假如函数参数中第二个参数 isSorted
为 true,则申明事前已晓得数组有序,顺序会跑一个更快的算法;假如有第三个参数 iteratee,则对数组每一个元素迭代,对迭代今后的效果举行去重,然后返归去重后的数组(array 的子数组);别的,暴露的 API 中没 context 参数。
4、_.range
// 返回某一个范围内的数构成的数组
_.range = function(start, stop, step) {
if (stop == null) {
stop = start || 0;
start = 0;
}
step = step || 1;
// 返回数组的长度
var length = Math.max(Math.ceil((stop - start) / step), 0);
var range = Array(length); // 返回的数组
for (var idx = 0; idx < length; idx++, start += step) {
range[idx] = start;
}
return range;
};
_.range([start], stop, [step]) 是一个用来建立整数天真编号的列表的函数,便于each 和 map轮回。假如省略start则默以为 0;step 默以为 1.返回一个从start 到stop的整数的列表,用step来增添 (或削减)独有。值得注意的是,假如stop值在start前面(也就是stop值小于start值),那末值域会被以为是零长度,而不是负增长。-假如要一个负数的值域 ,则运用负数step. (参考http://www.css88.com/doc/unde…)
5、_.bind
_.bind = function(func, context) {
if (nativeBind && func.bind === nativeBind)
// 假如浏览器支撑 ES5 bind 要领,而且 func 上的 bind 要领没有被重写,则优先运用原生的 bind 要领
return nativeBind.apply(func, slice.call(arguments, 1));
if (!_.isFunction(func))
//假如传入的参数 func 不是要领,则抛出毛病
throw new TypeError('Bind must be called on a function');
//典范闭包,函数返回函数
var args = slice.call(arguments, 2); // args 猎取优先运用的参数
var bound = function() {
//终究函数的现实挪用参数由两部份构成
//一部份是传入 _.bind 的参数(会被优先挪用),另一部份是传入 bound(_.bind 所返回要领)的参数
return executeBound(func, bound, context, this, args.concat(slice.call(arguments)));
};
return bound;
};
该要领是ES5 bind 要领的扩大, 将 func 中的 this 指向 context(对象),用法为_.bind(function, object, *arguments)
,个中arguments 参数可选,它会被看成 func 的参数传入,func 在挪用时,会优先用 arguments 参数,然后运用 _.bind 返回要领所传入的参数。
6、 _.memoize
_.memoize = function(func, hasher) {
var memoize = function(key) { // 贮存变量,轻易运用
var cache = memoize.cache;
//求 key
//假如传入了 hasher,则用 hasher 函数来盘算 key,不然用 参数 key(即 memoize 要领传入的第一个参数)当 key
var address = '' + (hasher ? hasher.apply(this, arguments) : key);
//假如这个 key 还没被 hash 过(还没求过值)
if (!_.has(cache, address)) cache[address] = func.apply(this, arguments);
return cache[address];
};
memoize.cache = {}; // cache 对象被当作 key-value 键值对缓存中心运算效果
return memoize; // 返回一个函数(典范闭包)
};
Memoizes要领能够缓存某函数的盘算效果,用法:_.memoize(function, [hashFunction])
假如传递了 hashFunction 参数,就用 hashFunction 的返回值作为key存储函数的盘算效果。hashFunction 默许运用function的第一个参数作为key。memoized值的缓存可作为返回函数的cache属性。
7、_.throttle
_.throttle = function(func, wait, options) {
var context, args, result;
var timeout = null; //标记时候戳,上一次实行回调的时候戳
var previous = 0;
if (!options) //假如没有传入 options 参数
options = {}; // 则将 options 参数置为空对象
var later = function() {
//假如 options.leading === false,则每次触发还调后将 previous 置为 0,不然置为当前时候戳
previous = options.leading === false ? 0 : _.now();
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
};
// _.throttle 要领返回的函数
return function() {
var now = _.now(); // 纪录当前时候戳
//第一次实行回调(此时 previous 为 0,今后 previous 值为上一次时候戳)
//而且假如顺序设定第一个回调不是立时实行的(options.leading === false),则将 previous 值(示意上次实行的时候戳)设为 now 的时候戳(第一次触发时),示意刚实行过,此次就不必实行了
if (!previous && options.leading === false) previous = now;
// 距离下次触发 func 还须要守候的时候
var remaining = wait - (now - previous);
context = this;
args = arguments;
if (remaining <= 0 || remaining > wait) {
if (timeout) {
clearTimeout(timeout); // 消除援用,防备内存泄漏
timeout = null;
}
// 重置前一次触发的时候戳
previous = now;
// 触发要领,result 为该要领返回值
result = func.apply(context, args);
if (!timeout)
context = args = null; //援用置为空,防备内存泄漏
} else if (!timeout && options.trailing !== false) {// 末了一次须要触发的状况
//假如已存在一个定时器,则不会进入该 if 分支
// 假如 {trailing: false},即末了一次不须要触发了,也不会进入这个分支
timeout = setTimeout(later, remaining); // 距离 remaining milliseconds 后触发 later 要领
}
return result; // 回调返回值
};
};
函数撙节(假如有一连事宜相应,则每距离肯定时候段触发),每距离 wait(Number) milliseconds 触发一次 func 要领,假如 options 参数传入 {leading: false},不会立时触发(守候 wait milliseconds 后第一次触发 func),假如 options 参数传入 {trailing: false},那末末了一次回调不会被触发。options 不能同时设置 leading 和 trailing 为 false。
8、_.debounce
_.debounce = function(func, wait, immediate) {
var timeout, args, context, timestamp, result;
var later = function() {
//定时器设置的回调 later 要领的触发时候,和一连事宜触发的末了一次时候戳的距离
var last = _.now() - timestamp; //假如距离为 wait(或许恰好大于 wait),则触发事宜
if (last < wait && last >= 0) { //时候距离 last 在 [0, wait) 中,还没到触发的点,则继承设置定时器
timeout = setTimeout(later, wait - last);
} else { //到了能够触发的时候点
timeout = null;
// 假如不是立时实行,随即实行 func 要领
if (!immediate) {
result = func.apply(context, args); // 实行 func 函数
if (!timeout) context = args = null;
}
}
};
return function() {
context = this;
args = arguments;
//每次触发函数,更新时候戳
timestamp = _.now();
// 立时触发须要满足两个前提
// immediate 参数为 true,而且 timeout 还没设置
var callNow = immediate && !timeout; // 设置 wait seconds 后触发 later 要领
// 在某一段的一连触发中,只会在第一次触发时进入这个 if 分支中
if (!timeout) // 设置了 timeout,所以今后不会进入这个 if 分支了
timeout = setTimeout(later, wait);
// 假如是立时触发
if (callNow) {
result = func.apply(context, args);
context = args = null; // 消除援用
}
return result;
};
};
函数去抖(一连事宜触发完毕后只触发一次),如_.debounce(function(){}, 1000)
示意一连事宜完毕后的 1000ms 后触发。
9、 _.pick
_.pick = function(object, oiteratee, context) {
var result = {}, // result 为返回的对象副本
obj = object, iteratee, keys;
if (obj == null) return result;
// 假如第二个参数是函数
if (_.isFunction(oiteratee)) {
keys = _.allKeys(obj);
iteratee = optimizeCb(oiteratee, context);
} else {
// 假如第二个参数不是函数
// 则背面的 keys 多是数组
// 也多是一连的几个并列的参数
// 用 flatten 将它们睁开
keys = flatten(arguments, false, false, 1);
//也转为 predicate 函数推断情势,将指定 key 转化为 predicate 函数
iteratee = function(value, key, obj) { return key in obj; };
obj = Object(obj);
}
for (var i = 0, length = keys.length; i < length; i++) {
var key = keys[i];
var value = obj[key];
if (iteratee(value, key, obj)) result[key] = value;
}
return result;
};
_.pick(object, *keys)依据肯定的需求(key 值,或许经由过程 predicate 函数返回真假),返回具有肯定键值对的对象副本。第二个参数能够是一个 predicate 函数,也能够是0个或多个key。
10、_.noConflict
_.noConflict = function() {
root._ = previousUnderscore;
return this;
};
假如全局环境中已运用了 _
变量,能够用该要领返回其他变量,继承运用 underscore 中的要领。
11、_.template
_.template = function(text, settings, oldSettings) {
// 兼容旧版本
if (!settings && oldSettings) settings = oldSettings;
settings = _.defaults({}, settings, _.templateSettings);
// Combine delimiters into one regular expression via alternation.
// // 正则表达式 pattern,用于正则婚配 text 字符串中的模板字符串
var matcher = RegExp([
(settings.escape || noMatch).source,
(settings.interpolate || noMatch).source,
(settings.evaluate || noMatch).source
].join('|') + '|$', 'g');
// Compile the template source, escaping string literals appropriately.
// 编译模板字符串,将原始的模板字符串替换成函数字符串
// 用拼接成的函数字符串天生函数(new Function(...))
var index = 0;
// source 变量拼接的字符串用来天生函数,用于当作 new Function 天生函数时的函数字符串变量
var source = "__p+='";
// replace 函数不须要为返回值赋值,重要是为了在函数内对 source 变量赋值
// // 将 text 变量中的模板提取出来
text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
// escape/interpolate/evaluate 为婚配的子表达式(假如没有婚配胜利则为 undefined)
// offset 为字符婚配(match)的肇端位置(偏移量)
source += text.slice(index, offset).replace(escaper, escapeChar);
index = offset + match.length; // 转变 index 值,为了下次的 slice
if (escape) {
// 须要对变量举行编码(=> HTML 实体编码)
// 防备 XSS 进击
source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
} else if (interpolate) { // 纯真的插进去变量
source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
} else if (evaluate) {
source += "';\n" + evaluate + "\n__p+='";
}
// Adobe VMs need the match returned to produce the correct offest.
// return 的作用是将婚配到的内容原样返回(Adobe VMs 须要返回 match 来使得 offset 值平常)
return match;
});
source += "';\n";
// If a variable is not specified, place data values in local scope.
// 假如设置了 settings.variable,能明显提拔模板的衬着速率,不然,默许用 with 语句指定作用域
if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
// 增添 print 功用
source = "var __t,__p='',__j=Array.prototype.join," +
"print=function(){__p+=__j.call(arguments,'');};\n" +
source + 'return __p;\n'; // __p 为返回的字符串
try {
// render 要领,前两个参数为 render 要领的参数
// obj 为传入的 JSON 对象,传入 _ 参数使得函数内部能用 Underscore 的函数
var render = new Function(settings.variable || 'obj', '_', source);
} catch (e) {
e.source = source;
throw e;
}
// 返回的函数
//data 平常是 JSON 数据,用来衬着模板
var template = function(data) {
// render 为模板衬着函数
return render.call(this, data, _); // 传入参数 _ ,使得模板里 <% %> 里的代码能用 underscore 的要领
};
// Provide the compiled source as a convenience for precompilation.
var argument = settings.variable || 'obj';
template.source = 'function(' + argument + '){\n' + source + '}';
return template;
};
_.template(templateString, [settings]) // 将 JavaScript 模板编译为能够用于页面显现的函数,setting 参数能够用来自定义字符串模板,是一个哈希表包括任何能够掩盖的设置。