Underscore源码分析

7/3/2015

Underscore.js是JavaScript的一个函数库,定义了100多个函数用于实际场景中对象,列表,数据的处理。除了可以在浏览器环境使用,Underscore.js还可以引入Node.js这类Server端中使用。这个区别在Underscore的第一行代码中做了区分。Underscor.js使用一个下划线(_)作为实例对象,函数库的所有方法都属于这个对象。方法大致上可以分成:集合(collection)、数组(array)、函数(function)、对象(object)和工具(utility)五大类。

比如类似于Python中的map函数,Underscore定义了自己的map函数用于JavaScript列表的映射操作

_.map([1, 2, 3], function(num){ return num * 3; });
//输出[3, 6, 9]

对于一个1000多行的源代码程序,100多个函数代码的实现,通过阅读其中的函数处理逻辑对于学好JavaScript是很有帮助的。其中的一些设计理念也可以借鉴到日常的代码编写过程中。

对象初始化

Underscore的初始化主要完成了Underscore对象"_"的创建。

获取全局根对象(针对前端的是"window",后端node则为"global")。如果自己开发前后端兼顾的代码,也需要清楚的明白调用的全局对象到底是哪个。

 var root = typeof self === 'object' && self.self === self && self ||typeof global === 'object' && global.global === global && global;

如果在引入 "__" 之前已经定义了,则将其原始的对象保存在previousUnderscore中。

另外将常用的函数存入变量中,不用每次调用的时候查询。这在《Effective JavaScript:编写高质量JavaScript代码的68个有效方法》 中有详细的介绍如何改进代码质量。

var previousUnderscore = root._;

var ArrayProto = Array.prototype, ObjProto = Object.prototype;
var push = ArrayProto.push,
    slice = ArrayProto.slice,
    toString = ObjProto.toString,
    hasOwnProperty = ObjProto.hasOwnProperty;
var nativeIsArray = Array.isArray,
    nativeKeys = Object.keys,
    nativeCreate = Object.create;
var Ctor = function(){};  //创建一个空的对象以备后续使用

以单例模式,创建Underscore对象,并根据是否包含有exports对象判定为后端代码调用还是前端的代码调用。

var _ = function(obj) {
   if (obj instanceof _) return obj;
   if (!(this instanceof _)) return new _(obj);
   this._wrapped = obj;
};
if (typeof exports !== 'undefined') {
  if (typeof module !== 'undefined' && module.exports) {
    exports = module.exports = _;
  }
  exports._ = _;
}else{
  root._ = _;
}
_.VERSION = '1.8.3';

定义回调函数,定义了回调函数调用的上下文对象环境context,并根据参数来调用不同的回调函数,call和apply都是基于对象的环境来执行函数func,区别在于一个只能是参数,另一个为参数数组

var optimizeCb = function(func, context, argCount) {
    if (context === void 0) return func;
    switch (argCount == null ? 3 : argCount) {
      case 1: return function(value) {
        return func.call(context, value);
      };
      case 2: return function(value, other) {
        return func.call(context, value, other);
      };
      case 3: return function(value, index, collection) {
        return func.call(context, value, index, collection);
      };
      case 4: return function(accumulator, value, index, collection) {
        return func.call(context, accumulator, value, index, collection);
      };
    }
    return function() {
      return func.apply(context, arguments);
    };
};

这里使用了多个underscore对象的方法比如identity 方法返回与传入的参数一样的值即 output=input,如果value是函数则调用上述的代码,如果是对象的话则调用matcher()方法来引入一个断言函数。否则默认调用_.property()函数来执行操作。

var cb = function(value, context, argCount) {
    if (value == null) return _.identity;
    if (_.isFunction(value)) return optimizeCb(value, context, argCount);
    if (_.isObject(value)) return _.matcher(value);
    return _.property(value);
 };
_.iteratee = function(value, context) {
  return cb(value, context, Infinity);
};

该段代码可参考ES6的RestArgs:用来计算传递给Array对象的参数集合

var restArgs = function(func, startIndex) {
    startIndex = startIndex == null ? func.length - 1 : +startIndex;
    return function() {
      var length = Math.max(arguments.length - startIndex, 0);
      var rest = Array(length);
      for (var index = 0; index < length; index++) {
        rest[index] = arguments[index + startIndex];
      }
      switch (startIndex) {
        case 0: return func.call(this, rest);
        case 1: return func.call(this, arguments[0], rest);
        case 2: return func.call(this, arguments[0], arguments[1], rest);
      }
      var args = Array(startIndex + 1);
      for (index = 0; index < startIndex; index++) {
        args[index] = arguments[index];
      }
      args[startIndex] = rest;
      return func.apply(this, args);
    };
  };

创建一个空对象并使其继承自prototype并返回Ctor为一个空function(){}对象。

  var baseCreate = function(prototype) {
    if (!_.isObject(prototype)) return {};
    if (nativeCreate) return nativeCreate(prototype);
    Ctor.prototype = prototype;
    var result = new Ctor;
    Ctor.prototype = null;
    return result;
  };

返回对象的键值为key的内容(void 0 应该就是 underfined)调用时property("key")(object)直接返回对象的键名为key的值

  var property = function(key) {
    return function(obj) {
      return obj == null ? void 0 : obj[key];
    };
  };

判断是否是类Array对象,注意不一定非要是Array,比如javascript的arguments本身不是一个数组,但拥有length属性。

  var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
  var getLength = property('length');
  var isArrayLike = function(collection) {
    var length = getLength(collection);
    return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
  };
总结

上述基本上完成了整个的Underscore代码的初始化工作,总结一下就是

  1. 获取全局对象
  2. 初始化原始javascript对象的调用方式比如缓存级联式的方法调用对象。
  3. 定义"_"对象,并可全局调用
  4. 定义回调函数和创建一些常用的对象比如空对象,和常用的方法比如isArrayLike property等。

Javascript 页面已被访问2293次

发表评论