Underscore.js 源码分析(第七出)【完】

前言

这是 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 = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#x27;',
    '`': '&#x60;'
};
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 _;
    });
}