// 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 中每一个元素执行处理器方法
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 之间
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 : 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 > 1;
iterator(array[mid]) = 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 = 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 " ' \
_.escape = function(string) {
return ('' + string).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 : //g,
// 直接输出变量的界定符
interpolate : //g,
// 需要将HTML输出为字符串(将特殊符号转换为字符串形式)的界定符
escape : //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函数
登录后复制