前言
这是 Underscore.js 源码分析的最后一篇,这篇主要是功能函数。
/* 实用功能 * ----------------- */ // 使用其它变量来表示 underscore(防止与其它库冲突) _.noConflict = function () { // 把其它库(previousUnderscore)的引用重新赋值给 root._ root._ = previousUnderscore; // 返回 underscore 对象(因为 _ 调用了 noConflict() 方法,所以 this 是指向 underscore), // 我们把 _.noConflict() 的返回值保存到一个变量中,这个变量就是 underscore 对象的引用,从而解决了不同库之间变量名冲突的问题 return this; }; // 值返回 _.identity = function (value) { return value; }; // 函数传入值的匿名函数 _.constant = function (value) { return function () { return value; }; }; // 定义一个空函数 _.noop = function () { }; // 根据 key 路径获取对应的值 _.property = function (path) { if (!_.isArray(path)) { return shallowProperty(path); } return function (obj) { return deepGet(obj, path); }; }; // 获取值 _.propertyOf = function (obj) { if (obj == null) { return function () { }; } return function (path) { return !_.isArray(path) ? obj[path] : deepGet(obj, path); }; }; // Returns a predicate for checking whether an object has a given set of // `key:value` pairs. _.matcher = _.matches = function (attrs) { attrs = _.extendOwn({}, attrs); return function (obj) { return _.isMatch(obj, attrs); }; }; // 生成一个长度为 n 次的,第项元素都为 _.times = function (n, iteratee, context) { var accum = Array(Math.max(0, n)); iteratee = optimizeCb(iteratee, context, 1); for (var i = 0; i < n; i++) accum[i] = iteratee(i); return accum; }; // 生成一个介于 min, max 之间的随机数 _.random = function (min, max) { if (max == null) { max = min; min = 0; } return min + Math.floor(Math.random() * (max - min + 1)); }; // 时间戳获取函数 _.now = Date.now || function () { return new Date().getTime(); }; // List of HTML entities for escaping. var escapeMap = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', '`': '`' }; var unescapeMap = _.invert(escapeMap); // HTML 转化通用函数 var createEscaper = function (map) { var escaper = function (match) { return map[match]; }; // 拼接正则字符串 var source = '(?:' + _.keys(map).join('|') + ')'; // 创建正则 var testRegexp = RegExp(source); var replaceRegexp = RegExp(source, 'g'); return function (string) { // 处理传入的字符串 string = string == null ? '' : '' + string; // 如果字符串通过了 testRegexp 正则的检测,则返回被替换后的 string,否则直接返回 string return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string; }; }; // 转义HTML字符串,替换&, <, >, ", ', 和 /字符。 _.escape = createEscaper(escapeMap); // 转义HTML字符串 _.unescape = createEscaper(unescapeMap); // 如果对象 obj 中的属性 property 是函数, 则调用它, 否则, 返回它 // fallback 为检没失败后的回调 _.result = function (obj, path, fallback) { // 如果 path 不是数组,则转化成数组形式 if (!_.isArray(path)) path = [path]; var length = path.length; // 如果 length 长度为零 if (!length) { // 如果 fllback 是函数,则执行,否则返回 fallback return _.isFunction(fallback) ? fallback.call(obj) : fallback; } for (var i = 0; i < length; i++) { // 如果 obj 为 null 返回 undefined,否则返回 obj[path[i]] var prop = obj == null ? void 0 : obj[path[i]]; // 如果 prop 为 undefined if (prop === void 0) { // 把 fallback 赋值给 prop, prop = fallback; i = length; // Ensure we don't continue iterating. } // 如果 prop 是函数,则执行 prop 方法,否则把 prop 赋值给 obj obj = _.isFunction(prop) ? prop.call(obj) : prop; } return obj; }; // 生成唯一ID var idCounter = 0; _.uniqueId = function (prefix) { var id = ++idCounter + ''; return prefix ? prefix + id : id; }; // 默认的模板分隔符正则 _.templateSettings = { evaluate: /<%([\s\S]+?)%>/g, interpolate: /<%=([\s\S]+?)%>/g, escape: /<%-([\s\S]+?)%>/g }; // 无匹配的正则 var noMatch = /(.)^/; // Certain characters need to be escaped so that they can be put into a // string literal. var escapes = { "'": "'", '\\': '\\', '\r': 'r', '\n': 'n', '\u2028': 'u2028', '\u2029': 'u2029' }; var escapeRegExp = /\\|'|\r|\n|\u2028|\u2029/g; // 返回字符 var escapeChar = function (match) { return '\\' + escapes[match]; }; // 模板解析引擎 _.template = function (text, settings, oldSettings) { // 如果 settings 不正在,并且 oldSettings 存在,则把 oldSettings 赋值给 settings if (!settings && oldSettings) settings = oldSettings; // 全并 settings,_.templateSettings settings = _.defaults({}, settings, _.templateSettings); // 生成正则. 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. var index = 0; var source = "__p+='"; text.replace(matcher, function (match, escape, interpolate, evaluate, offset) { // match:匹配到的项 // escape:匹配到的 escape 模板 // interpolate:匹配到的 interpolate 模板 // evaluate:匹配到的 evaluate 模板 // offset:在字符串中的下标 // 每一乒匹配成功都会调用一次些函数 source += text.slice(index, offset).replace(escapeRegExp, escapeChar); // 记录下一次 slice 开始的下标 index = offset + match.length; // 对三种模板作不同的处理 if (escape) { 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 offset. return match; }); source += "';\n"; // If a variable is not specified, place data values in local scope. if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; // 拼接函数字符串 source = "var __t,__p='',__j=Array.prototype.join," + "print=function(){__p+=__j.call(arguments,'');};\n" + source + 'return __p;\n'; var render; try { // 创建 render 函数,函数有两个参数 (settings.variable || 'obj', '_') render = new Function(settings.variable || 'obj', '_', source); } catch (e) { e.source = source; throw e; } var template = function (data) { return render.call(this, data, _); }; // Provide the compiled source as a convenience for precompilation. var argument = settings.variable || 'obj'; template.source = 'function(' + argument + '){\n' + source + '}'; // 返回模板函数,需要再次调用并传入相关参数才会最终返回 html 字符串 return template; }; // 生成一个链式调用的方法(实例) _.chain = function (obj) { // 用 _() 方法包装 obj 对象,即:把 obj 挂载到 _ 的实例上 var instance = _(obj); instance._chain = true; return instance; }; /* OOP * --------------- */ // 判断是否需要链式调用. var chainResult = function (instance, obj) { // 如果 instance 上的 _chain 为真,则返回 _(obj).chain() 的执行结果,否则直接返回 obj // 如果 instance._chain 为真值,那么每次调用都会返回一个新实例 // 这里的 _(obj).chain() 中的 .chain() 没有传入参数,是不是很不解? // 其实 _.chain 方法并不像你看到的那样: // _.chain = function(obj) { // var instance = _(obj); // instance._chain = true; // return instance; // } // 它已经在 _.minx() 方法中被生写了,不仅仅只是这个方法,所以的定义在 _ 上的静态方法都被生写到了 _.prototype 上 return instance._chain ? _(obj).chain() : obj; }; // 添加自定义的函数到 _ 上 _.mixin = function (obj) { _.each(_.functions(obj), function (name) { var func = _[name] = obj[name]; _.prototype[name] = function () { var args = [this._wrapped]; // 把实例上的 _wrapped 和 arguments 作合并,合并完之后,通过apply() 调用方法时 _wrapped 作为第一个参数传入 push.apply(args, arguments); return chainResult(this, func.apply(_, args)); }; }); return _; }; // 这里传入了 underscore 执行一了一次,主要作用是把 _ 对象上的静态方法添加到 _ 的原型上 // 这就是为什么前面的 chainResult 方法中的 _(obj).chain() 不会报错原因 // 还有一点,为什么 _(obj).chain() 这里的 .chain() 方法可以不传参数? // 明明前面定义的 _.chain() 是需要传个对象作为参数的。 // 要想解开这个迷,我们读一下 _.mixin() 方法的实现就知道了。在把 _ 上的所有方法挂载到 prototype 上的同时,也对参数作了处理。 // _.mixin(_); // 在 _ 的原型上定义数组的中一些同名方法('pop'、'push'、'reverse'、'shift'、'sort'、'splice'、'unshift') _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function (name) { // 缓存方法 var method = ArrayProto[name]; _.prototype[name] = function () { // 缓存实例上的对象 var obj = this._wrapped; // 把 obj 作为第一个参数传入并执行方法 method.apply(obj, arguments); if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0]; // return chainResult(this, obj); }; }); // 在 _ 原型上定义 'concat'、'join'、'slice' 方法 _.each(['concat', 'join', 'slice'], function (name) { var method = ArrayProto[name]; _.prototype[name] = function () { return chainResult(this, method.apply(this._wrapped, arguments)); }; }); // 从链式调用中提取结果值. _.prototype.value = function () { return this._wrapped; }; // 定义 _ 原型上的 valueOf、toJSON、value 方法 _.prototype.valueOf = _.prototype.toJSON = _.prototype.value; // 定义 _ 原型上的 toString 方法 _.prototype.toString = function () { return String(this._wrapped); }; if (typeof define == 'function' && define.amd) { define('underscore', [], function () { return _; }); }