// Underscore.js 1.3.3
// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
// Underscore is freely distributable under the MIT license.
// Portions of Underscore are inspired or borrowed from Prototype,
// Oliver Steele’s Functional, and John Resig’s Micro-Templating.
// For all details and documentation:
// http://documentcloud.github.com/underscore
(function() {
// 建立一个全局对象, 在浏览器中示意为window对象, 在Node.js中示意global对象
var root = this;
// 保留"_"(下划线变量)被掩盖之前的值
// 假如涌现定名争执或斟酌到范例, 可经由历程_.noConflict()要领恢复"_"被Underscore占用之前的值, 并返回Underscore对象以便从新定名
var previousUnderscore = root._;
// 建立一个空的对象常量, 便于内部同享运用
var breaker = {};
// 将内置对象的原型链缓存在局部变量, 轻易疾速挪用
var ArrayProto = Array.prototype, //
ObjProto = Object.prototype, //
FuncProto = Function.prototype;
// 将内置对象原型中的经常使用要领缓存在局部变量, 轻易疾速挪用
var slice = ArrayProto.slice, //
unshift = ArrayProto.unshift, //
toString = ObjProto.toString, //
hasOwnProperty = ObjProto.hasOwnProperty;
// 这里定义了一些JavaScript 1.6供应的新要领
// 假如宿主环境中支撑这些要领则优先挪用, 假如宿主环境中没有供应, 则会由Underscore完成
var nativeForEach = ArrayProto.forEach, //
nativeMap = ArrayProto.map, //
nativeReduce = ArrayProto.reduce, //
nativeReduceRight = ArrayProto.reduceRight, //
nativeFilter = ArrayProto.filter, //
nativeEvery = ArrayProto.every, //
nativeSome = ArrayProto.some, //
nativeIndexOf = ArrayProto.indexOf, //
nativeLastIndexOf = ArrayProto.lastIndexOf, //
nativeIsArray = Array.isArray, //
nativeKeys = Object.keys, //
nativeBind = FuncProto.bind;
// 建立对象式的挪用体式格局, 将返回一个Underscore包装器, 包装器对象的原型中包含Underscore一切要领(类似与将DOM对象包装为一个jQuery对象)
var _ = function(obj) {
// 一切Underscore对象在内部均经由历程wrapper对象举行组织
return new wrapper(obj);
};
// 针对差别的宿主环境, 将Undersocre的定名变量寄存到差别的对象中
if( typeof exports !== 'undefined') {// Node.js环境
if( typeof module !== 'undefined' && module.exports) {
exports = module.exports = _;
}
exports._ = _;
} else {// 浏览器环境中Underscore的定名变量被挂在window对象中
root['_'] = _;
}
// 版本声明
_.VERSION = '1.3.3';
// 鸠合相干的要领(数据和对象的通用处置惩罚要领)
// --------------------
// 迭代处置惩罚器, 对鸠合中每一个元素实行处置惩罚器要领
var each = _.each = _.forEach = function(obj, iterator, context) {
// 不处置惩罚空值
if(obj == null)
return;
if(nativeForEach && obj.forEach === nativeForEach) {
// 假如宿主环境支撑, 则优先挪用JavaScript 1.6供应的forEach要领
obj.forEach(iterator, context);
} else if(obj.length === +obj.length) {
// 对<数组>中每一个元素实行处置惩罚器要领
for(var i = 0, l = obj.length; i < l; i++) {
if( i in obj && iterator.call(context, obj[i], i, obj) === breaker)
return;
}
} else {
// 对<对象>中每一个元素实行处置惩罚器要领
for(var key in obj) {
if(_.has(obj, key)) {
if(iterator.call(context, obj[key], key, obj) === breaker)
return;
}
}
}
};
// 迭代处置惩罚器, 与each要领的差别在于map会存储每次迭代的返回值, 并作为一个新的数组返回
_.map = _.collect = function(obj, iterator, context) {
// 用于寄存返回值的数组
var results = [];
if(obj == null)
return results;
// 优先挪用宿主环境供应的map要领
if(nativeMap && obj.map === nativeMap)
return obj.map(iterator, context);
// 迭代处置惩罚鸠合中的元素
each(obj, function(value, index, list) {
// 将每次迭代处置惩罚的返回值存储到results数组
results[results.length] = iterator.call(context, value, index, list);
});
// 返回处置惩罚效果
if(obj.length === +obj.length)
results.length = obj.length;
return results;
};
// 将鸠合中每一个元素放入迭代处置惩罚器, 并将本次迭代的返回值作为"memo"通报到下一次迭代, 平常用于累计效果或衔接数据
_.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
// 经由历程参数数目搜检是不是存在初始值
var initial = arguments.length > 2;
if(obj == null)
obj = [];
// 优先挪用宿主环境供应的reduce要领
if(nativeReduce && obj.reduce === nativeReduce && false) {
if(context)
iterator = _.bind(iterator, context);
return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
}
// 迭代处置惩罚鸠合中的元素
each(obj, function(value, index, list) {
if(!initial) {
// 假如没有初始值, 则将第一个元素作为初始值; 假如被处置惩罚的是对象鸠合, 则默许值为第一个属性的值
memo = value;
initial = true;
} else {
// 纪录处置惩罚效果, 并将效果通报给下一次迭代
memo = iterator.call(context, memo, value, index, list);
}
});
if(!initial)
throw new TypeError('Reduce of empty array with no initial value');
return memo;
};
// 与reduce作用类似, 将逆向迭代鸠合中的元素(即从末了一个元素最先直到第一个元素)
_.reduceRight = _.foldr = function(obj, iterator, memo, context) {
var initial = arguments.length > 2;
if(obj == null)
obj = [];
// 优先挪用宿主环境供应的reduceRight要领
if(nativeReduceRight && obj.reduceRight === nativeReduceRight) {
if(context)
iterator = _.bind(iterator, context);
return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
}
// 逆转鸠合中的元素递次
var reversed = _.toArray(obj).reverse();
if(context && !initial)
iterator = _.bind(iterator, context);
// 经由历程reduce要领处置惩罚数据
return initial ? _.reduce(reversed, iterator, memo, context) : _.reduce(reversed, iterator);
};
// 遍历鸠合中的元素, 返回第一个能够经由历程处置惩罚器考证的元素
_.find = _.detect = function(obj, iterator, context) {
// result寄存第一个能够经由历程考证的元素
var result;
// 经由历程any要领遍历数据, 并纪录经由历程考证的元素
// (假如是在迭代中搜检处置惩罚器返回状况, 这里运用each要领会更适宜)
any(obj, function(value, index, list) {
// 假如处置惩罚器返回的效果被转换为Boolean范例后值为true, 则当前纪录并返回当前元素
if(iterator.call(context, value, index, list)) {
result = value;
return true;
}
});
return result;
};
// 与find要领作用类似, 但filter要领会纪录下鸠合中一切经由历程考证的元素
_.filter = _.select = function(obj, iterator, context) {
// 用于存储经由历程考证的元素数组
var results = [];
if(obj == null)
return results;
// 优先挪用宿主环境供应的filter要领
if(nativeFilter && obj.filter === nativeFilter)
return obj.filter(iterator, context);
// 迭代鸠合中的元素, 并将经由历程处置惩罚器考证的元素放到数组中并返回
each(obj, function(value, index, list) {
if(iterator.call(context, value, index, list))
results[results.length] = value;
});
return results;
};
// 与filter要领作用相反, 即返回没有经由历程处置惩罚器考证的元素列表
_.reject = function(obj, iterator, context) {
var results = [];
if(obj == null)
return results;
each(obj, function(value, index, list) {
if(!iterator.call(context, value, index, list))
results[results.length] = value;
});
return results;
};
// 假如鸠合中一切元素均能经由历程处置惩罚器考证, 则返回true
_.every = _.all = function(obj, iterator, context) {
var result = true;
if(obj == null)
return result;
// 优先挪用宿主环境供应的every要领
if(nativeEvery && obj.every === nativeEvery)
return obj.every(iterator, context);
// 迭代鸠合中的元素
each(obj, function(value, index, list) {
// 这里理解为 result = (result && iterator.call(context, value, index, list))
// 考证处置惩罚器的效果被转换为Boolean范例后是不是为true值
if(!( result = result && iterator.call(context, value, index, list)))
return breaker;
});
return !!result;
};
// 搜检鸠合中任何一个元素在被转换为Boolean范例时, 是不是为true值?或许经由历程处置惩罚器处置惩罚后, 是不是值为true?
var any = _.some = _.any = function(obj, iterator, context) {
// 假如没有指定处置惩罚器参数, 则默许的处置惩罚器函数会返回元素本身, 并在迭代时经由历程将元素转换为Boolean范例来推断是不是为true值
iterator || ( iterator = _.identity);
var result = false;
if(obj == null)
return result;
// 优先挪用宿主环境供应的some要领
if(nativeSome && obj.some === nativeSome)
return obj.some(iterator, context);
// 迭代鸠合中的元素
each(obj, function(value, index, list) {
if(result || ( result = iterator.call(context, value, index, list)))
return breaker;
});
return !!result;
};
// 搜检鸠合中是不是有值与目标参数完整婚配(同时将婚配数据范例)
_.include = _.contains = function(obj, target) {
var found = false;
if(obj == null)
return found;
// 优先挪用宿主环境供应的Array.prototype.indexOf要领
if(nativeIndexOf && obj.indexOf === nativeIndexOf)
return obj.indexOf(target) != -1;
// 经由历程any要领迭代鸠合中的元素, 考证元素的值和范例与目标是不是完整婚配
found = any(obj, function(value) {
return value === target;
});
return found;
};
// 顺次挪用鸠合中一切元素的同名要领, 从第3个参数最先, 将被以此传入到元素的挪用要领中
// 返回一个数组, 存储了一切要领的处置惩罚效果
_.invoke = function(obj, method) {
// 挪用同名要领时通报的参数(从第3个参数最先)
var args = slice.call(arguments, 2);
// 顺次挪用每一个元素的要领, 并将效果放入数组中返回
return _.map(obj, function(value) {
return (_.isFunction(method) ? method || value : value[method]).apply(value, args);
});
};
// 遍历一个由对象列表构成的数组, 并返回每一个对象中的指定属性的值列表
_.pluck = function(obj, key) {
// 假如某一个对象中不存在该属性, 则返回undefined
return _.map(obj, function(value) {
return value[key];
});
};
// 返回鸠合中的最大值, 假如不存在可比较的值, 则返回undefined
_.max = function(obj, iterator, context) {
// 假如鸠合是一个数组, 且没有运用处置惩罚器, 则运用Math.max猎取最大值
// 平常会是在一个数组存储了一系列Number范例的数据
if(!iterator && _.isArray(obj) && obj[0] === +obj[0])
return Math.max.apply(Math, obj);
// 关于空值, 直接返回负无穷大
if(!iterator && _.isEmpty(obj))
return -Infinity;
// 一个暂时的对象, computed用于在比较历程当中存储最大值(暂时的)
var result = {
computed : -Infinity
};
// 迭代鸠合中的元素
each(obj, function(value, index, list) {
// 假如指定了处置惩罚器参数, 则比较的数据为处置惩罚器返回的值, 不然直接运用each遍用时的默许值
var computed = iterator ? iterator.call(context, value, index, list) : value;
// 假如比较值比拟上一个值要大, 则将当前值放入result.value
computed >= result.computed && ( result = {
value : value,
computed : computed
});
});
// 返回最大值
return result.value;
};
// 返回鸠合中的最小值, 处置惩罚历程与max要领一致
_.min = function(obj, iterator, context) {
if(!iterator && _.isArray(obj) && obj[0] === +obj[0])
return Math.min.apply(Math, obj);
if(!iterator && _.isEmpty(obj))
return Infinity;
var result = {
computed : Infinity
};
each(obj, function(value, index, list) {
var computed = iterator ? iterator.call(context, value, index, list) : value;
computed < result.computed && ( result = {
value : value,
computed : computed
});
});
return result.value;
};
// 经由历程随机数, 让数组不必分列
_.shuffle = function(obj) {
// shuffled变量存储处置惩罚历程及终究的效果数据
var shuffled = [], rand;
// 迭代鸠合中的元素
each(obj, function(value, index, list) {
// 天生一个随机数, 随机数在<0-当前已处置惩罚的数目>之间
rand = Math.floor(Math.random() * (index + 1));
// 将已随机取得的元素放到shuffled数组末端
shuffled[index] = shuffled[rand];
// 在前面取得的随机数的位置插进去最新值
shuffled[rand] = value;
});
// 返回一个数组, 该数组中存储了经由随机混排的鸠合元素
return shuffled;
};
// 对鸠合中元素, 依据特定的字段或值举行分列
// 比拟Array.prototype.sort要领, sortBy要领支撑对对象排序
_.sortBy = function(obj, val, context) {
// val应当是对象的一个属性, 或一个处置惩罚器函数, 假如是一个处置惩罚器, 则应当返回须要举行比较的数据
var iterator = _.isFunction(val) ? val : function(obj) {
return obj[val];
};
// 挪用递次: _.pluck(_.map().sort());
// 挪用_.map()要领遍历鸠合, 并将鸠合中的元素放到value节点, 将元素中须要举行比较的数据放到criteria属性中
// 挪用sort()要领将鸠合中的元素依据criteria属性中的数据举行递次排序
// 挪用pluck猎取排序后的对象鸠兼并返回
return _.pluck(_.map(obj, function(value, index, list) {
return {
value : value,
criteria : iterator.call(context, value, index, list)
};
}).sort(function(left, right) {
var a = left.criteria, b = right.criteria;
if(a ===
void 0)
return 1;
if(b ===
void 0)
return -1;
return a < b ? -1 : a > b ? 1 : 0;
}), 'value');
};
// 将鸠合中的元素, 按处置惩罚器返回的key分为多个数组
_.groupBy = function(obj, val) {
var result = {};
// val将被转换为举行分组的处置惩罚器函数, 假如val不是一个Function范例的数据, 则将被作为挑选元素时的key值
var iterator = _.isFunction(val) ? val : function(obj) {
return obj[val];
};
// 迭代鸠合中的元素
each(obj, function(value, index) {
// 将处置惩罚器的返回值作为key, 并将雷同的key元素放到一个新的数组
var key = iterator(value, index);
(result[key] || (result[key] = [])).push(value);
});
// 返回已分组的数据
return result;
};
_.sortedIndex = function(array, obj, iterator) {
iterator || ( iterator = _.identity);
var low = 0, high = array.length;
while(low < high) {
var mid = (low + high) >> 1;
iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid;
}
return low;
};
// 将一个鸠合转换一个数组并返回
// 平常用于将arguments转换为数组, 或将对象无序鸠合转换为数据情势的有序鸠合
_.toArray = function(obj) {
if(!obj)
return [];
if(_.isArray(obj))
return slice.call(obj);
// 将arguments转换为数组
if(_.isArguments(obj))
return slice.call(obj);
if(obj.toArray && _.isFunction(obj.toArray))
return obj.toArray();
// 将对象转换为数组, 数组中包含对象中一切属性的值列表(不包含对象原型链中的属性)
return _.values(obj);
};
// 盘算鸠合中元素的数目
_.size = function(obj) {
// 假如鸠合是一个数组, 则盘算数组元素数目
// 假如鸠合是一个对象, 则盘算对象中的属性数目(不包含对象原型链中的属性)
return _.isArray(obj) ? obj.length : _.keys(obj).length;
};
// 数组相干的要领
// ---------------
// 返回一个数组的第一个或顺序指定的n个元素
_.first = _.head = _.take = function(array, n, guard) {
// 假如没有指定参数n, 则返回第一个元素
// 假如指定了n, 则返回一个新的数组, 包含递次指定数目n个元素
// guard参数用于肯定只返回第一个元素, 当guard为true时, 指定数目n无效
return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
};
// 返回一个新数组, 包含除第一个元素外的别的元素, 或消除从末了一个元素最先向前指定n个元素
// 与first要领差别在于, first肯定须要的元素在数组之前的位置, initial肯定能消除的元素在数组末了的位置
_.initial = function(array, n, guard) {
// 假如没有通报参数n, 则默许返回除末了一个元素外的别的元素
// 假如通报参数n, 则返回从末了一个元素最先向前的n个元素外的别的元素
// guard用于肯定只返回一个元素, 当guard为true时, 指定数目n无效
return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n));
};
// 返回数组的末了一个或倒序指定的n个元素
_.last = function(array, n, guard) {
if((n != null) && !guard) {
// 盘算并指定猎取的元素位置n, 直到数组末端, 作为一个新的数组返回
return slice.call(array, Math.max(array.length - n, 0));
} else {
// 假如没有指定数目, 或guard为true时, 只返回末了一个元素
return array[array.length - 1];
}
};
// 猎取除了第一个或指定前n个元素外的别的元素
_.rest = _.tail = function(array, index, guard) {
// 盘算slice的第二个位置参数, 直到数组末端
// 假如没有指定index, 或guard值为true, 则返回除第一个元素外的别的元素
// (index == null)值为true时, 作为参数通报给slice函数将被自动转换为1
return slice.call(array, (index == null) || guard ? 1 : index);
};
// 返回数组中一切值能被转换为true的元素, 返回一个新的数组
// 不能被转换的值包含 false, 0, '', null, undefined, NaN, 这些值将被转换为false
_.compact = function(array) {
return _.filter(array, function(value) {
return !!value;
});
};
// 将一个多维数组合成为一维数组, 支撑深层兼并
// shallow参数用于掌握兼并深度, 当shallow为true时, 只兼并第一层, 默许举行深层兼并
_.flatten = function(array, shallow) {
// 迭代数组中的每一个元素, 并将返回值作为demo通报给下一次迭代
return _.reduce(array, function(memo, value) {
// 假如元素依然是一个数组, 举行以下推断:
// - 假如不举行深层兼并, 则运用Array.prototype.concat将当前数组和之前的数据举行衔接
// - 假如支撑深层兼并, 则迭代挪用flatten要领, 直到底层元素不再是数组范例
if(_.isArray(value))
return memo.concat( shallow ? value : _.flatten(value));
// 数据(value)已处于底层, 不再是数组范例, 则将数据兼并到memo中并返回
memo[memo.length] = value;
return memo;
}, []);
};
// 挑选并返回当前数组中与指定数据不相称的差别数据(可参考difference要领解释)
_.without = function(array) {
return _.difference(array, slice.call(arguments, 1));
};
// 对数组中的数据举行去重(运用===举行比较)
// 当isSorted参数不为false时, 将顺次对数组中的元素挪用include要领, 搜检雷同元素是不是已被增加到返回值(数组)中
// 假如挪用之前确保数组中数据按递次分列, 则能够将isSorted设为true, 它将经由历程与末了一个元素举行对照来消除雷同值, 运用isSorted效力会高于默许的include体式格局
// uniq要领默许将以数组中的数据举行对照, 假如声明iterator处置惩罚器, 则会依据处置惩罚器建立一个对照数组, 比较时以该数组中的数据为准, 但终究返回的唯一数据仍然是原始数组
_.uniq = _.unique = function(array, isSorted, iterator) {
// 假如运用了iterator处置惩罚器, 则先将当前数组中的数据会先经由按迭代器处置惩罚, 并返回一个处置惩罚后的新数组
// 新数组用于作为比较的基准
var initial = iterator ? _.map(array, iterator) : array;
// 用于纪录处置惩罚效果的暂时数组
var results = [];
// 假如数组中只要2个值, 则不须要运用include要领举行比较, 将isSorted设置为true能进步运转效力
if(array.length < 3)
isSorted = true;
// 运用reduce要领迭代并累加处置惩罚效果
// initial变量是须要举行比较的基准数据, 它多是原始数组, 也多是处置惩罚器的效果鸠合(假如设置过iterator)
_.reduce(initial, function(memo, value, index) {
// 假如isSorted参数为true, 则直接运用===比较纪录中的末了一个数据
// 假如isSorted参数为false, 则运用include要领与鸠合中的每一个数据举行对照
if( isSorted ? _.last(memo) !== value || !memo.length : !_.include(memo, value)) {
// memo纪录了已比较过的无反复数据
// 依据iterator参数的状况, memo中纪录的数据多是原始数据, 也多是处置惩罚器处置惩罚后的数据
memo.push(value);
// 处置惩罚效果数组中保留的一向为原始数组中的数据
results.push(array[index]);
}
return memo;
}, []);
// 返回处置惩罚效果, 它只包含数组中无反复的数据
return results;
};
// union要领与uniq要领作用一致, 差别之处在于union许可在参数中传入多个数组
_.union = function() {
// union对参数中的多个数组举行浅层兼并为一个数组对象通报给uniq要领举行处置惩罚
return _.uniq(_.flatten(arguments, true));
};
// 猎取当前数组与别的一个或多个数组的交集元素
// 从第二个参数最先为须要举行比较的一个或多个数组
_.intersection = _.intersect = function(array) {
// rest变量纪录须要举行比较的别的数组对象
var rest = slice.call(arguments, 1);
// 运用uniq要领去除当前数组中的反复数据, 防止反复盘算
// 对当前数组的数据经由历程处置惩罚器举行过滤, 并返回相符前提(比较雷同元素)的数据
return _.filter(_.uniq(array), function(item) {
// 运用every要领考证每一个数组中都包含了须要对照的数据
// 假如一切数组中均包含对照数据, 则悉数返回true, 假如恣意一个数组没有包含该元素, 则返回false
return _.every(rest, function(other) {
// other参数存储了每一个须要举行对照的数组
// item存储了当前数组中须要举行对照的数据
// 运用indexOf要领搜刮数组中是不是存在该元素(可参考indexOf要领解释)
return _.indexOf(other, item) >= 0;
});
});
};
// 挑选并返回当前数组中与指定数据不相称的差别数据
// 该函数平常用于删除数组中指定的数据, 并取得删除后的新数组
// 该要领的作用与without相称, without要领参数情势上不许可数据被包含在数组中, 而difference要领参数情势上发起是数组(也能够和without运用雷同情势的参数)
_.difference = function(array) {
// 对第2个参数最先的一切参数, 作为一个数组举行兼并(仅兼并第一层, 而并不是深层兼并)
// rest变量存储考证数据, 在本要领中用于与原数据对照
var rest = _.flatten(slice.call(arguments, 1), true);
// 对兼并后的数组数据举行过滤, 过滤前提是当前数组中不包含参数指定的考证数据的内容
// 将相符过滤前提的数据组合为一个新的数组并返回
return _.filter(array, function(value) {
return !_.include(rest, value);
});
};
// 将每一个数组的雷同位置的数据作为一个新的二维数组返回, 返回的数组长度以传入参数中最大的数组长度为准, 别的数组的空缺位置运用undefined添补
// zip要领应当包含多个参数, 且每一个参数应当均为数组
_.zip = function() {
// 将参数转换为数组, 此时args是一个二维数组
var args = slice.call(arguments);
// 盘算每一个数组的长度, 并返回个中最大长度值
var length = _.max(_.pluck(args, 'length'));
// 遵照最大长度值建立一个新的空数组, 该数组用于存储处置惩罚效果
var results = new Array(length);
// 轮回最大长度, 在每次轮回将挪用pluck要领猎取每一个数组中雷同位置的数据(顺次从0到末了位置)
// 将猎取到的数据存储在一个新的数组, 放入results并返回
for(var i = 0; i < length; i++)
results[i] = _.pluck(args, "" + i);
// 返回的效果是一个二维数组
return results;
};
// 搜刮一个元素在数组中初次涌现的位置, 假如元素不存在则返回 -1
// 搜刮时运用 === 对元素举行婚配
_.indexOf = function(array, item, isSorted) {
if(array == null)
return -1;
var i, l;
if(isSorted) {
i = _.sortedIndex(array, item);
return array[i] === item ? i : -1;
}
// 优先挪用宿主环境供应的indexOf要领
if(nativeIndexOf && array.indexOf === nativeIndexOf)
return array.indexOf(item);
// 轮回并返回元素初次涌现的位置
for( i = 0, l = array.length; i < l; i++)
if( i in array && array[i] === item)
return i;
// 没有找到元素, 返回-1
return -1;
};
// 返回一个元素在数组中末了一次涌现的位置, 假如元素不存在则返回 -1
// 搜刮时运用 === 对元素举行婚配
_.lastIndexOf = function(array, item) {
if(array == null)
return -1;
// 优先挪用宿主环境供应的lastIndexOf要领
if(nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf)
return array.lastIndexOf(item);
var i = array.length;
// 轮回并返回元素末了涌现的位置
while(i--)
if( i in array && array[i] === item)
return i;
// 没有找到元素, 返回-1
return -1;
};
// 依据区间和步长, 天生一系列整数, 并作为数组返回
// start参数示意最小数
// stop参数示意最大数
// step参数示意天生多个数值之间的步长值
_.range = function(start, stop, step) {
// 参数掌握
if(arguments.length <= 1) {
// 假如没有参数, 则start = 0, stop = 0, 在轮回中不会天生任何数据, 将返回一个空数组
// 假如有1个参数, 则参数指定给stop, start = 0
stop = start || 0;
start = 0;
}
// 天生整数的步长值, 默许为1
step = arguments[2] || 1;
// 依据区间和步长盘算将天生的最大值
var len = Math.max(Math.ceil((stop - start) / step), 0);
var idx = 0;
var range = new Array(len);
// 天生整数列表, 并存储到range数组
while(idx < len) {
range[idx++] = start;
start += step;
}
// 返回列表效果
return range;
};
// 函数相干要领
// ------------------
// 建立一个用于设置prototype的大众函数对象
var ctor = function() {
};
// 为一个函数绑定实行高低文, 任何状况下挪用该函数, 函数中的this均指向context对象
// 绑定函数时, 能够同时给函数通报挪用形参
_.bind = function bind(func, context) {
var bound, args;
// 优先挪用宿主环境供应的bind要领
if(func.bind === nativeBind && nativeBind)
return nativeBind.apply(func, slice.call(arguments, 1));
// func参数必需是一个函数(Function)范例
if(!_.isFunction(func))
throw new TypeError;
// args变量存储了bind要领第三个最先的参数列表, 每次挪用时都将通报给func函数
args = slice.call(arguments, 2);
return bound = function() {
if(!(this instanceof bound))
return func.apply(context, sargs.concat(slice.call(arguments)));
ctor.prototype = func.prototype;
var self = new ctor;
var result = func.apply(self, args.concat(slice.call(arguments)));
if(Object(result) === result)
return result;
return self;
};
};
// 将指定的函数, 或对象本身的一切函数高低本绑定到对象本身, 被绑定的函数在被挪用时, 高低文对象一向指向对象本身
// 该要领平常在处置惩罚对象事宜时运用, 比方:
// _(obj).bindAll(); // 或_(obj).bindAll('handlerClick');
// document.addEventListener('click', obj.handlerClick);
// 在handlerClick要领中, 高低文依然是obj对象
_.bindAll = function(obj) {
// 第二个参数最先示意须要绑定的函数称号
var funcs = slice.call(arguments, 1);
// 假如没有指定特定的函数称号, 则默许绑定对象本身一切范例为Function的属性
if(funcs.length == 0)
funcs = _.functions(obj);
// 轮回并将一切的函数高低本设置为obj对象本身
// each要领本身不会遍历对象原型链中的要领, 但此处的funcs列表是经由历程_.functions要领猎取的, 它已包含了原型链中的要领
each(funcs, function(f) {
obj[f] = _.bind(obj[f], obj);
});
return obj;
};
// memoize要领将返回一个函数, 该函数集成了缓存功用, 将经由盘算的值缓存到局部变量并在下次挪用时直接返回
// 假如盘算效果是一个巨大的对象或数据, 运用时应当斟酌内存占用状况
_.memoize = function(func, hasher) {
// 用于存储缓存效果的memo对象
var memo = {};
// hasher参数应当是一个function, 它用于返回一个key, 该key作为读取缓存的标识
// 假如没有指定key, 则默许运用函数的第一个参数作为key, 假如函数的第一个参数是复合数据范例, 能够会返回类似[Object object]的key, 这个key能够会形成后续盘算的数据不准确
hasher || ( hasher = _.identity);
// 返回一个函数, 该函数起首经由历程搜检缓存, 再对没有缓存过的数据举行挪用
return function() {
var key = hasher.apply(this, arguments);
return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
};
};
// 延时实行一个函数
// wait单元为ms, 第3个参数最先将被顺次通报给实行函数
_.delay = function(func, wait) {
var args = slice.call(arguments, 2);
return setTimeout(function() {
return func.apply(null, args);
}, wait);
};
// 耽误实行函数
// JavaScript中的setTimeout会被放到一个零丁的函数客栈中实行, 实行时刻是在当前客栈中挪用的函数都被实行终了以后
// defer设置函数在1ms后实行, 目标是将func函数放到零丁的客栈中, 守候当前函数实行完成后再实行
// defer要领平常用于处置惩罚DOM操纵的优先级, 完成准确的逻辑流程和更流通的交互体验
_.defer = function(func) {
return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
};
// 函数撙节要领, throttle要领重要用于掌握函数的实行频次, 在被掌握的时刻距离内, 频仍挪用函数不会被屡次实行
// 在时刻距离内假如屡次挪用了函数, 时刻隔住手时会自动挪用一次, 不须要比及时刻住手后再手动挪用(自动挪用时不会有返回值)
// throttle函数平常用于处置惩罚庞杂和挪用频仍的函数, 经由历程撙节掌握函数的挪用频次, 节约处置惩罚资本
// 比方window.onresize绑定的事宜函数, 或element.onmousemove绑定的事宜函数, 能够用throttle举行包装
// throttle要领返回一个函数, 该函数会自动挪用func并举行撙节掌握
_.throttle = function(func, wait) {
var context, args, timeout, throttling, more, result;
// whenDone变量挪用了debounce要领, 因而在屡次一连挪用函数时, 末了一次挪用会掩盖之前挪用的定时器, 消灭状况函数也仅会被实行一次
// whenDone函数在末了一次函数实行的时刻距离住手时挪用, 消灭撙节和挪用历程当中纪录的一些状况
var whenDone = _.debounce(function() {
more = throttling = false;
}, wait);
// 返回一个函数, 并在函数内举行撙节掌握
return function() {
// 保留函数的实行高低文和参数
context = this;
args = arguments;
// later函数在上一次函数挪用时刻距离住手时实行
var later = function() {
// 消灭timeout句柄, 轻易下一次函数挪用
timeout = null;
// more纪录了在上一次挪用至时刻距离住手之间, 是不是反复挪用了函数
// 假如反复挪用了函数, 在时刻距离住手时将自动再次挪用函数
if(more)
func.apply(context, args);
// 挪用whenDone, 用于在时刻距离后消灭撙节状况
whenDone();
};
// timeout纪录了上一次函数实行的时刻距离句柄
// timeout时刻距离住手时挪用later函数, later中将消灭timeout, 并搜检是不是须要再次挪用函数
if(!timeout)
timeout = setTimeout(later, wait);
// throttling变量纪录上次挪用的时刻距离是不是已完毕, 即是不是处于撙节历程当中
// throttling在每次函数挪用时设为true, 示意须要举行撙节, 在时刻距离住手时设置为false(在whenDone函数中完成)
if(throttling) {
// 撙节历程当中举行了屡次挪用, 在more中纪录一个状况, 示意在时刻距离住手时须要再次自动挪用函数
more = true;
} else {
// 没有处于撙节历程, 多是第一次挪用函数, 或已凌驾上一次挪用的距离, 能够直接挪用函数
result = func.apply(context, args);
}
// 挪用whenDone, 用于在时刻距离后消灭撙节状况
whenDone();
// throttling变量纪录函数挪用时的撙节状况
throttling = true;
// 返回挪用效果
return result;
};
};
// debounce与throttle要领类似, 用于函数撙节, 它们的差别之处在于:
// -- throttle关注函数的实行频次, 在指定频次内函数只会被实行一次;
// -- debounce函数更关注函数实行的距离, 即函数两次的挪用时刻不能小于指定时刻;
// 假如两次函数的实行距离小于wait, 定时器会被消灭并从新建立, 这意味着一连频仍地挪用函数, 函数一向不会被实行, 直到某一次挪用与上一次挪用的时刻不小于wait毫秒
// debounce函数平常用于掌握须要一段时刻以后才实行的操纵, 比方在用户输入终了200ms后提醒用户, 能够运用debounce包装一个函数, 绑定到onkeyup事宜
// ----------------------------------------------------------------
// @param {Function} func 示意被实行的函数
// @param {Number} wait 示意许可的时刻距离, 在该时刻局限内反复挪用会被从新推延wait毫秒
// @param {Boolean} immediate 示意函数挪用后是不是马上实行, true为马上挪用, false为在时刻住手时挪用
// debounce要领返回一个函数, 该函数会自动挪用func并举行撙节掌握
_.debounce = function(func, wait, immediate) {
// timeout用于纪录函数上一次挪用的实行状况(定时器句柄)
// 当timeout为null时, 示意上一次挪用已完毕
var timeout;
// 返回一个函数, 并在函数内举行撙节掌握
return function() {
// 坚持函数的高低文对象和参数
var context = this, args = arguments;
var later = function() {
// 设置timeout为null
// later函数会在许可的时刻住手时被挪用
// 挪用该函数时, 表明上一次函数实行时刻已凌驾了商定的时刻距离, 此时以后再举行挪用都是被许可的
timeout = null;
if(!immediate)
func.apply(context, args);
};
// 假如函数被设定为马上实行, 且上一次挪用的时刻距离已过去, 则马上挪用函数
if(immediate && !timeout)
func.apply(context, args);
// 建立一个定时器用于搜检和设置函数的挪用状况
// 建立定时器之前先清空上一次setTimeout句柄, 不管上一次绑定的函数是不是已被实行
// 假如本次函数在挪用时, 上一次函数实行还没有最先(平常是immediate设置为false时), 则函数的实行时刻会被推延, 因而timeout句柄会被从新建立
clearTimeout(timeout);
// 在许可的时刻住手时挪用later函数
timeout = setTimeout(later, wait);
};
};
// 建立一个只会被实行一次的函数, 假如该函数被反复挪用, 将返回第一次实行的效果
// 该函数用于猎取和盘算牢固数据的逻辑, 如猎取用户所用的浏览器范例
_.once = function(func) {
// ran纪录函数是不是被实行过
// memo纪录函数末了一次实行的效果
var ran = false, memo;
return function() {
// 假如函数已被实行过, 则直接返回第一次实行的效果
if(ran)
return memo;
ran = true;
return memo = func.apply(this, arguments);
};
};
// 返回一个函数, 该函数会将当前函数作为参数通报给一个包裹函数
// 在包裹函数中能够经由历程第一个参数挪用当前函数, 并返回效果
// 平常用于多个流程处置惩罚函数的低耦合组合挪用
_.wrap = function(func, wrapper) {
return function() {
// 将当前函数作为第一个参数, 通报给wrapper函数
var args = [func].concat(slice.call(arguments, 0));
// 返回wrapper函数的处置惩罚效果
return wrapper.apply(this, args);
};
};
// 将多个函数组合到一同, 依据参数通报的递次, 后一个函数的返回值会被一次作为参数通报给前一个函数作为参数继承处置惩罚
// _.compose(A, B, C); 等同于 A(B(C()));
// 该要领的瑕玷在于被关联的函数处置惩罚的参数数目只能有一个, 假如须要通报多个参数, 能够经由历程Array或Object复合数据范例举行组装
_.compose = function() {
// 猎取函数列表, 一切参数需均为Function范例
var funcs = arguments;
// 返回一个供挪用的函数句柄
return function() {
// 从后向前顺次实行函数, 并将纪录的返回值作为参数通报给前一个函数继承处置惩罚
var args = arguments;
for(var i = funcs.length - 1; i >= 0; i--) {
args = [funcs[i].apply(this, args)];
}
// 返回末了一次挪用函数的返回值
return args[0];
};
};
// 返回一个函数, 该函数作为挪用计数器, 当该函数被挪用times次(或凌驾times次)后, func函数将被实行
// after要领平常用作异步的计数器, 比方在多个AJAX要求悉数完成后须要实行一个函数, 则能够运用after在每一个AJAX要求完成后挪用
_.after = function(times, func) {
// 假如没有指定或指定无效次数, 则func被直接挪用
if(times <= 0)
return func();
// 返回一个计数器函数
return function() {
// 每次挪用计数器函数times减1, 挪用times次以后实行func函数并返回func函数的返回值
if(--times < 1) {
return func.apply(this, arguments);
}
};
};
// 对象相干要领
// ----------------
// 猎取一个对象的属性名列表(不包含原型链中的属性)
_.keys = nativeKeys ||
function(obj) {
if(obj !== Object(obj))
throw new TypeError('Invalid object');
var keys = [];
// 纪录并返回对象的一切属性名
for(var key in obj)
if(_.has(obj, key))
keys[keys.length] = key;
return keys;
};
// 返回一个对象中一切属性的值列表(不包含原型链中的属性)
_.values = function(obj) {
return _.map(obj, _.identity);
};
// 猎取一个对象中一切属性值为Function范例的key列表, 并按key名举行排序(包含原型链中的属性)
_.functions = _.methods = function(obj) {
var names = [];
for(var key in obj) {
if(_.isFunction(obj[key]))
names.push(key);
}
return names.sort();
};
// 将一个或多个对象的属性(包含原型链中的属性), 复制到obj对象, 假如存在同名属性则掩盖
_.extend = function(obj) {
// each轮回参数中的一个或多个对象
each(slice.call(arguments, 1), function(source) {
// 将对象中的悉数属性复制或掩盖到obj对象
for(var prop in source) {
obj[prop] = source[prop];
}
});
return obj;
};
// 返回一个新对象, 并从obj中复制指定的属性到新对象中
// 第2个参数最先为指定的须要复制的属性名(支撑多个参数和深层数组)
_.pick = function(obj) {
// 建立一个对象, 寄存复制的指定属性
var result = {};
// 从第二个参数最先兼并为一个寄存属性名列表的数组
each(_.flatten(slice.call(arguments, 1)), function(key) {
// 轮回属性名列表, 假如obj中存在该属性, 则将其复制到result对象
if( key in obj)
result[key] = obj[key];
});
// 返回复制效果
return result;
};
// 将obj中不存在或转换为Boolean范例后值为false的属性, 从参数中指定的一个或多个对象中复制到obj
// 平常用于给对象指定默许值
_.defaults = function(obj) {
// 从第二个参数最先可指定多个对象, 这些对象中的属性将被顺次复制到obj对象中(假如obj对象中不存在该属性的话)
each(slice.call(arguments, 1), function(source) {
// 遍历每一个对象中的一切属性
for(var prop in source) {
// 假如obj中不存在或属性值转换为Boolean范例后值为false, 则将属性复制到obj中
if(obj[prop] == null)
obj[prop] = source[prop];
}
});
return obj;
};
// 建立一个obj的副本, 返回一个新的对象, 该对象包含obj中的一切属性和值的状况
// clone函数不支撑深层复制, 比方obj中的某个属性寄存着一个对象, 则该对象不会被复制
// 假如obj是一个数组, 则会建立一个雷同的数组对象
_.clone = function(obj) {
// 不支撑非数组和对象范例的数据
if(!_.isObject(obj))
return obj;
// 复制并返回数组或对象
return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
};
// 实行一个函数, 并将obj作为参数通报给该函数, 函数实行终了后终究返回obj对象
// 平常在建立一个要领链的时刻会运用tap要领, 比方:
// _(obj).chain().tap(click).tap(mouseover).tap(mouseout);
_.tap = function(obj, interceptor) {
interceptor(obj);
return obj;
};
// eq函数只在isEqual要领中挪用, 用于比较两个数据的值是不是相称
// 与 === 差别在于, eq更关注数据的值
// 假如举行比较的是两个复合数据范例, 不单单议比较是不是来自同一个援用, 且会举行深层比较(对两个对象的构造和数据举行比较)
function eq(a, b, stack) {
// 搜检两个简朴数据范例的值是不是相称
// 关于复合数据范例, 假如它们来自同一个援用, 则以为其相称
// 假如被比较的值个中包含0, 则搜检另一个值是不是为-0, 由于 0 === -0 是建立的
// 而 1 / 0 == 1 / -0 是不建立的(1 / 0值为Infinity, 1 / -0值为-Infinity, 而Infinity不等于-Infinity)
if(a === b)
return a !== 0 || 1 / a == 1 / b;
// 将数据转换为布尔范例后假如值为false, 将推断两个值的数据范例是不是相称(由于null与undefined, false, 0, 空字符串, 在非严厉比较下值是相称的)
if(a == null || b == null)
return a === b;
// 假如举行比较的数据是一个Underscore封装的对象(具有_chain属性的对象被以为是Underscore对象)
// 则将对象解封后猎取本身的数据(经由历程_wrapped接见), 然后再对本身的数据举行比较
// 它们的关联类似与一个jQuery封装的DOM对象, 和浏览器本身建立的DOM对象
if(a._chain)
a = a._wrapped;
if(b._chain)
b = b._wrapped;
// 假如对象供应了自定义的isEqual要领(此处的isEqual要领并不是Undersocre对象的isEqual要领, 由于在上一步已对Undersocre对象举行了解封)
// 则运用对象自定义的isEqual要领与另一个对象举行比较
if(a.isEqual && _.isFunction(a.isEqual))
return a.isEqual(b);
if(b.isEqual && _.isFunction(b.isEqual))
return b.isEqual(a);
// 对两个数据的数据范例举行考证
// 猎取对象a的数据范例(经由历程Object.prototype.toString要领)
var className = toString.call(a);
// 假如对象a的数据范例与对象b不婚配, 则以为两个数据值也不婚配
if(className != toString.call(b))
return false;
// 实行到此处, 能够确保须要比较的两个数据均为复合数据范例, 且数据范例相称
// 经由历程switch搜检数据的数据范例, 针对差别数据范例举行差别的比较
// (此处不包含对数组和对象范例, 由于它们能够包含更深条理的数据, 将在背面举行深层比较)
switch (className) {
case '[object String]':
// 假如被比较的是字符串范例(个中a的是经由历程new String()建立的字符串)
// 则将B转换为String对象后举行婚配(这里婚配并不是举行严厉的数据范例搜检, 由于它们并不是来自同一个对象的援用)
// 在挪用 == 举行比较时, 会自动挪用对象的toString()要领, 返回两个简朴数据范例的字符串
return a == String(b);
case '[object Number]':
// 经由历程+a将a转成一个Number, 假如a被转换之前与转换以后不相称, 则以为a是一个NaN范例
// 由于NaN与NaN是不相称的, 因而当a值为NaN时, 没法简朴地运用a == b举行婚配, 而是用雷同的要领搜检b是不是为NaN(即 b != +b)
// 当a值是一个非NaN的数据时, 则搜检a是不是为0, 由于当b为-0时, 0 === -0是建立的(现实上它们在逻辑上属于两个差别的数据)
return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b);
case '[object Date]':
// 对日期范例没有运用return或break, 因而会继承实行到下一步(不管数据范例是不是为Boolean范例, 由于下一步将对Boolean范例举行搜检)
case '[object Boolean]':
// 将日期或布尔范例转换为数字
// 日期范例将转换为数值范例的时刻戳(无效的日期花样将被换转为NaN)
// 布尔范例中, true被转换为1, false被转换为0
// 比较两个日期或布尔范例被转换为数字后是不是相称
return +a == +b;
case '[object RegExp]':
// 正则表达式范例, 经由历程source接见表达式的字符串情势
// 搜检两个表达式的字符串情势是不是相称
// 搜检两个表达式的全局属性是不是雷同(包含g, i, m)
// 假如完整相称, 则以为两个数据相称
return a.source == b.source && a.global == b.global && a.multiline == b.multiline && a.ignoreCase == b.ignoreCase;
}
// 当实行到此时, ab两个数据应当为范例雷同的对象或数组范例
if( typeof a != 'object' || typeof b != 'object')
return false;
// stack(堆)是在isEqual挪用eq函数时内部通报的空数组, 在背面比较对象和数据的内部迭代中挪用eq要领也会通报
// length纪录堆的长度
var length = stack.length;
while(length--) {
// 假如堆中的某个对象与数据a婚配, 则以为相称
if(stack[length] == a)
return true;
}
// 将数据a增加到堆中
stack.push(a);
// 定义一些局部变量
var size = 0, result = true;
// 经由历程递归深层比较对象和数组
if(className == '[object Array]') {
// 被比较的数据为数组范例
// size纪录数组的长度
// result比较两个数组的长度是不是一致, 假如长度不一致, 则要领的末了将返回result(即false)
size = a.length;
result = size == b.length;
// 假如两个数组的长度一致
if(result) {
// 挪用eq要领对数组中的元素举行迭代比较(假如数组中包含二维数组或对象, eq要领会举行深层比较)
while(size--) {
// 在确保两个数组都存在当前索引的元素时, 挪用eq要领深层比较(将堆数据通报给eq要领)
// 将比较的效果存储到result变量, 假如result为false(即在比较中取得某个元素的数据不一致), 则住手迭代
if(!( result = size in a == size in b && eq(a[size], b[size], stack)))
break;
}
}
} else {
// 被比较的数据为对象范例
// 假如两个对象不是同一个类的实例(经由历程constructor属性比较), 则以为两个对象不相称
if('constructor' in a != 'constructor' in b || a.constructor != b.constructor)
return false;
// 深层比较两个对象中的数据
for(var key in a) {
if(_.has(a, key)) {
// size用于纪录比较过的属性数目, 由于这里遍历的是a对象的属性, 并比较b对象中该属性的数据
// 当b对象中的属性数目过剩a对象时, 此处的逻辑建立, 但两个对象并不相称
size++;
// 迭代挪用eq要领, 深层比较两个对象中的属性值
// 将比较的效果纪录到result变量, 当比较到不相称的数据时住手迭代
if(!( result = _.has(b, key) && eq(a[key], b[key], stack)))
break;
}
}
// 深层比较终了, 这里已能够确保在对象a中的一切数据, 对象b中也存在雷同的数据
// 依据size(对象属性长度)搜检对象b中的属性数目是不是与对象a相称
if(result) {
// 遍历对象b中的一切属性
for(key in b) {
// 当size已到0时(即对象a中的属性数目已遍历终了), 而对象b中还存在有属性, 则对象b中的属性多于对象a
if(_.has(b, key) && !(size--))
break;
}
// 当对象b中的属性多于对象a, 则以为两个对象不相称
result = !size;
}
}
// 函数实行终了时, 从堆中移除第一个数据(在比较对象或数组时, 会迭代eq要领, 堆中能够存在多个数据)
stack.pop();
// 返回的result纪录了终究的比较效果
return result;
}
// 对两个数据的值举行比较(支撑复合数据范例), 内部函数eq的外部要领
_.isEqual = function(a, b) {
return eq(a, b, []);
};
// 搜检数据是不是为空值, 包含'', false, 0, null, undefined, NaN, 空数组(数组长度为0)和空对象(对象本身没有任何属性)
_.isEmpty = function(obj) {
// obj被转换为Boolean范例后值为false
if(obj == null)
return true;
// 搜检对象或字符串长度是不是为0
if(_.isArray(obj) || _.isString(obj))
return obj.length === 0;
// 搜检对象(运用for in轮回时将起首轮回对象本身的属性, 其次是原型链中的属性), 因而假如第一个属性是属于对象本身的, 那末该对象不是一个空对象
for(var key in obj)
if(_.has(obj, key))
return false;
// 一切数据范例均没有经由历程考证, 是一个空数据
return true;
};
// 考证对象是不是是一个DOM对象
_.isElement = function(obj) {
return !!(obj && obj.nodeType == 1);
};
// 考证对象是不是是一个数组范例, 优先挪用宿主环境供应的isArray要领
_.isArray = nativeIsArray ||
function(obj) {
return toString.call(obj) == '[object Array]';
};
// 考证对象是不是是一个复合数据范例的对象(即非基础数据范例String, Boolean, Number, null, undefined)
// 假如基础数据范例经由历程new举行建立, 则也属于对象范例
_.isObject = function(obj) {
return obj === Object(obj);
};
// 搜检一个数据是不是是一个arguments参数对象
_.isArguments = function(obj) {
return toString.call(obj) == '[object Arguments]';
};
// 考证isArguments函数, 假如运转环境没法一般考证arguments范例的数据, 则从新定义isArguments要领
if(!_.isArguments(arguments)) {
// 关于环境没法经由历程toString考证arguments范例的, 则经由历程挪用arguments独占的callee要领来举行考证
_.isArguments = function(obj) {
// callee是arguments的一个属性, 指向对arguments所属函数本身的援用
return !!(obj && _.has(obj, 'callee'));
};
}
// 考证对象是不是是一个函数范例
_.isFunction = function(obj) {
return toString.call(obj) == '[object Function]';
};
// 考证对象是不是是一个字符串范例
_.isString = function(obj) {
return toString.call(obj) == '[object String]';
};
// 考证对象是不是是一个数字范例
_.isNumber = function(obj) {
return toString.call(obj) == '[object Number]';
};
// 搜检一个数字是不是为有用数字且有用局限(Number范例, 值在负无穷大 - 正无穷大之间)
_.isFinite = function(obj) {
return _.isNumber(obj) && isFinite(obj);
};
// 搜检数据是不是为NaN范例(一切数据中只要NaN与NaN不相称)
_.isNaN = function(obj) {
return obj !== obj;
};
// 搜检数据是不是时Boolean范例
_.isBoolean = function(obj) {
// 支撑字面量和对象情势的Boolean数据
return obj === true || obj === false || toString.call(obj) == '[object Boolean]';
};
// 搜检数据是不是是一个Date范例
_.isDate = function(obj) {
return toString.call(obj) == '[object Date]';
};
// 搜检数据是不是是一个正则表达式范例
_.isRegExp = function(obj) {
return toString.call(obj) == '[object RegExp]';
};
// 搜检数据是不是是Null值
_.isNull = function(obj) {
return obj === null;
};
// 搜检数据是不是是Undefined(未定义的)值
_.isUndefined = function(obj) {
return obj ===
void 0;
};
// 搜检一个属性是不是属于对象本身, 而非原型链中
_.has = function(obj, key) {
return hasOwnProperty.call(obj, key);
};
// 东西函数
// -----------------
// 摒弃_(下划线)定名的Underscore对象, 并返回Underscore对象, 平常用于防止定名争执或范例定名体式格局
// 比方:
// var us = _.noConflict(); // 作废_(下划线)定名, 并将Underscore对象寄存于us变量中
// console.log(_); // _(下划线)已没法再接见Underscore对象, 而恢复为Underscore定义前的值
_.noConflict = function() {
// previousUnderscore变量纪录了Underscore定义前_(下划线)的值
root._ = previousUnderscore;
return this;
};
// 返回与参数雷同的值, 平常用于将一个数据的猎取体式格局转换为函数猎取体式格局(内部用于构建要领时作为默许处置惩罚器函数)
_.identity = function(value) {
return value;
};
// 使指定的函数迭代实行n次(无参数)
_.times = function(n, iterator, context) {
for(var i = 0; i < n; i++)
iterator.call(context, i);
};
// 将HTML字符串中的特别字符转换为HTML实体, 包含 & < > " ' \
_.escape = function(string) {
return ('' + string).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/\//g, '/');
};
// 指定一个对象的属性, 返回该属性对应的值, 假如该属性对应的是一个函数, 则会实行该函数并返回效果
_.result = function(object, property) {
if(object == null)
return null;
// 猎取对象的值
var value = object[property];
// 假如值是一个函数, 则实行并返回, 不然将直接返回
return _.isFunction(value) ? value.call(object) : value;
};
// 增加一系列自定义要领到Underscore对象中, 用于扩大Underscore插件
_.mixin = function(obj) {
// obj是一个鸠合一系列自定义要领的对象, 此处经由历程each遍历对象的要领
each(_.functions(obj), function(name) {
// 经由历程addToWrapper函数将自定义要领增加到Underscore构建的对象中, 用于支撑对象式挪用
// 同时将要领增加到 _ 本身, 用于支撑函数式挪用
addToWrapper(name, _[name] = obj[name]);
});
};
// 猎取一个全局唯一标识, 标识从0最先累加
var idCounter = 0;
// prefix示意标识的前缀, 假如没有指定前缀则直接返回标识, 平常用于给对象或DOM建立唯一ID
_.uniqueId = function(prefix) {
var id = idCounter++;
return prefix ? prefix + id : id;
};
// 定义模板的界定标记, 在template要领中运用
_.templateSettings = {
// JavaScript可实行代码的界定符
evaluate : /<%([\s\S]+?)%>/g,
// 直接输出变量的界定符
interpolate : /<%=([\s\S]+?)%>/g,
// 须要将HTML输出为字符串(将特别标记转换为字符串情势)的界定符
escape : /<%-([\s\S]+?)%>/g
};
var noMatch = /.^/;
// escapes对象纪录了须要举行相交换转的特别标记与字符串情势的对应关联, 在二者举行互相转换时作为索引运用
// 起首依据字符串情势定义特别字符
var escapes = {
'\\' : '\\',
"'" : "'",
'r' : '\r',
'n' : '\n',
't' : '\t',
'u2028' : '\u2028',
'u2029' : '\u2029'
};
// 遍历一切特别字符字符串, 并以特别字符作为key纪录字符串情势
for(var p in escapes)
escapes[escapes[p]] = p;
// 定义模板中须要替代的特别标记, 包含反斜杠, 单引号, 回车符, 换行符, 制表符, 行分隔符, 段落分隔符
// 在将字符串中的特别标记转换为字符串情势时运用
var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g;
// 在将字符串情势的特别标记举行反转(替代)时运用
var unescaper = /\\(\\|'|r|n|t|u2028|u2029)/g;
// 反转字符串中的特别标记
// 在模板中涉及到须要实行的JavaScript源码, 须要举行特别标记反转, 不然假如以HTML实体或字符串情势涌现, 会抛出语法错误
var unescape = function(code) {
return code.replace(unescaper, function(match, escape) {
return escapes[escape];
});
};
// Underscore模板剖析要领, 用于将数据添补到一个模板字符串中
// 模板剖析流程:
// 1. 将模板中的特别标记转换为字符串
// 2. 剖析escape情势标签, 将内容剖析为HTML实体
// 3. 剖析interpolate情势标签, 输出变量
// 4. 剖析evaluate情势标签, 建立可实行的JavaScript代码
// 5. 天生一个处置惩罚函数, 该函数在取得数据后可直接添补到模板并返回添补后的字符串
// 6. 依据参数返回添补后的字符串或处置惩罚函数的句柄
// -------------------
// 在模板体内, 可经由历程argments猎取2个参数, 分别为添补数据(称号为obj)和Underscore对象(称号为_)
_.template = function(text, data, settings) {
// 模板设置, 假如没有指定设置项, 则运用templateSettings中指定的设置项
settings = _.defaults(settings || {}, _.templateSettings);
// 最先将模板剖析为可实行源码
var source = "__p+='" + text.replace(escaper, function(match) {
// 将特别标记转移为字符串情势
return '\\' + escapes[match];
}).replace(settings.escape || noMatch, function(match, code) {
// 剖析escape情势标签 <%- %>, 将变量中包含的HTML经由历程_.escape函数转换为HTML实体
return "'+\n_.escape(" + unescape(code) + ")+\n'";
}).replace(settings.interpolate || noMatch, function(match, code) {
// 剖析interpolate情势标签 <%= %>, 将模板内容作为一个变量与别的字符串衔接起来, 则会作为一个变量输出
return "'+\n(" + unescape(code) + ")+\n'";
}).replace(settings.evaluate || noMatch, function(match, code) {
// 剖析evaluate情势标签 <% %>, evaluate标签中存储了须要实行的JavaScript代码, 这里完毕当前的字符串拼接, 并在新的一行作为JavaScript语法实行, 并将背面的内容再次作为字符串的最先, 因而evaluate标签内的JavaScript代码就可以被一般实行
return "';\n" + unescape(code) + "\n;__p+='";
}) + "';\n";
if(!settings.variable)
source = 'with(obj||{}){\n' + source + '}\n';
source = "var __p='';" + "var print=function(){__p+=Array.prototype.join.call(arguments, '')};\n" + source + "return __p;\n";
// 建立一个函数, 将源码作为函数实行体, 将obj和Underscore作为参数通报给该函数
var render = new Function(settings.variable || 'obj', '_', source);
// 假如指定了模板的添补数据, 则替代模板内容, 并返回替代后的效果
if(data)
return render(data, _);
// 假如没有指定添补数据, 则返回一个函数, 该函数用于将接收到的数据替代到模板
// 假如在顺序中会屡次添补雷同模板, 那末在第一次挪用时发起不指定添补数据, 在取得处置惩罚函数的援用后, 再直接挪用会进步运转效力
var template = function(data) {
return render.call(this, data, _);
};
// 将建立的源码字符串增加到函数对象中, 平常用于调试和测试
template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}';
// 没有指定添补数据的状况下, 返回处置惩罚函数句柄
return template;
};
// 支撑Underscore对象的要领链操纵, 可参考 wrapper.prototype.chain
_.chain = function(obj) {
return _(obj).chain();
};
// Underscore对象封装相干要领
// ---------------
// 建立一个包装器, 将一些原始数据举行包装
// 一切的undersocre对象, 内部均经由历程wrapper函数举行组织和封装
// Underscore与wrapper的内部关联:
// -内部定义变量_, 将Underscore相干的要领增加到_, 如许就可以够支撑函数式的挪用, 如_.bind()
// -内部定义wrapper类, 将_的原型对象指向wrapper类的原型
// -将Underscore相干的要领增加到wrapper原型, 建立的_对象就具有了Underscore的要领
// -将Array.prototype相干要领增加到wrapper原型, 建立的_对象就具有了Array.prototype中的要领
// -new _()时现实建立并返回了一个wrapper()对象, 并将原始数组存储到_wrapped变量, 并将原始值作为第一个参数挪用对应要领
var wrapper = function(obj) {
// 原始数据寄存在包装对象的_wrapped属性中
this._wrapped = obj;
};
// 将Underscore的原型对象指向wrapper的原型, 因而经由历程像wrapper原型中增加要领, Underscore对象也会具有一样的要领
_.prototype = wrapper.prototype;
// 返回一个对象, 假如当前Underscore挪用了chain()要领(即_chain属性为true), 则返回一个被包装的Underscore对象, 不然返回对象本身
// result函数用于在组织要领链时返回Underscore的包装对象
var result = function(obj, chain) {
return chain ? _(obj).chain() : obj;
};
// 将一个自定义要领增加到Underscore对象中(现实是增加到wrapper的原型中, 而Underscore对象的原型指向了wrapper的原型)
var addToWrapper = function(name, func) {
// 向wrapper原型中增加一个name函数, 该函数挪用func函数, 并支撑了要领链的处置惩罚
wrapper.prototype[name] = function() {
// 猎取func函数的参数, 并将当前的原始数据增加到第一个参数
var args = slice.call(arguments);
unshift.call(args, this._wrapped);
// 实行函数并返回效果, 并经由历程result函数对要领链举行封装, 假如当前挪用了chain()要领, 则返回封装后的Underscore对象, 不然返回对象本身
return result(func.apply(_, args), this._chain);
};
};
// 将内部定义的_(下划线, 即Underscore要领鸠合对象)中的要领复制到wrapper的原型链中(即Underscore的原型链中)
// 这是为了在组织对象式挪用的Underscore对象时, 这些对象也会具有内部定义的Underscore要领
_.mixin(_);
// 将Array.prototype中的相干要领增加到Underscore对象中, 因而在封装后的Underscore对象中也能够直接挪用Array.prototype中的要领
// 如: _([]).push()
each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
// 猎取Array.prototype中对应要领的援用
var method = ArrayProto[name];
// 将该要领增加到Underscore对象中(现实是增加到wrapper的原型对象, 因而在建立Underscore对象时同时具有了该要领)
wrapper.prototype[name] = function() {
// _wrapped变量中存储Underscore对象的原始值
var wrapped = this._wrapped;
// 挪用Array对应的要领并返回效果
method.apply(wrapped, arguments);
var length = wrapped.length;
if((name == 'shift' || name == 'splice') && length === 0)
delete wrapped[0];
// 即使是关于Array中的要领, Underscore一样支撑要领链操纵
return result(wrapped, this._chain);
};
});
// 作用同于上一段代码, 将数组中的一些要领增加到Underscore对象, 并支撑了要领链操纵
// 区分在于上一段代码所增加的函数, 均返回Array对象本身(也多是封装后的Array), concat, join, slice要领将返回一个新的Array对象(也多是封装后的Array)
each(['concat', 'join', 'slice'], function(name) {
var method = ArrayProto[name];
wrapper.prototype[name] = function() {
return result(method.apply(this._wrapped, arguments), this._chain);
};
});
// 对Underscore对象举行链式操纵的声明要领
wrapper.prototype.chain = function() {
// this._chain用来标示当前对象是不是运用链式操纵
// 关于支撑要领链操纵的数据, 平常在具体要领中会返回一个Underscore对象, 并将原始值寄存在_wrapped属性中, 也能够经由历程value()要领猎取原始值
this._chain = true;
return this;
};
// 返回被封装的Underscore对象的原始值(寄存在_wrapped属性中)
wrapper.prototype.value = function() {
return this._wrapped;
};
}).call(this);