IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    探究underscore源码(一)

    墨白发表于 2016-09-10 16:40:02
    love 0

    本文基于underscore v1.8.3版本

    源头

    一直想学习一下类库的源码,jQuery刚刚看到选择器那块,直接被那一大块正则搞懵逼了。经过同事的推荐,选择了underscore来作为类库研究的起点。

    闭包

    所有函数都在一个闭包内,避免污染全局变量,这没什么特殊的,略过。。。

    (function() { ...    
    }());

    全局对象的获取

    先看下面一段代码:

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

    self是什么鬼?global跟this都能够猜出来是全局变量,这个self从哪里冒出来的?

    第一眼看到这样的代码很困惑,感觉压根没有头绪。但是如果你打开chrome的控制台,神奇的事情发生了
    self变量

    其实查看源码的注释,我们也能看出来这段代码的作用:在不一样的环境里面获取当前全局对象this

    1. self | window:浏览器

    2. global:服务端

    3. this:某些虚拟机

    为了压缩所做的原型赋值

    源码中有将对象的原型链赋值给一个变量的做法:

    var ArrayProto = Array.prototype, ObjProto = Object.prototype;
    var SymbolProto = typeof Symbol !== 'undefined' ? Symbol.prototype : null;

    一开始我并没明白这么做的优势,代码不都一样吗?

    参考注释并且上网查资料才知道原因:为了压缩

    举个例子,Array.prototype是没有办法经过压缩的,Array,prototype这些,如果改了,浏览器就无法识别这些字段了。

    但经过类似上面代码的处理,ObjProto经过压缩就能变成变量a,那么原来的代码就会变成a.xxx。

    我们平常写的代码也可以进行类似上面的处理,只要代码的复用超过两次,就可以考虑将其赋值给一个变量了。

    this值统一处理

    this在类库中的应用很广泛,undersocre采用了一个内部函数来处理this:

      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);
          };
          // The 2-parameter case has been omitted only because no current consumers
          // made use of it.
          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);
        };
      };

    注意到上面的case语句没有2的情况,看其注释基本就能明白,这是因为没有使用到2的情况。

    上面函数的最后一个参数argCount是用来指定参数个数:

    1. 接受单值的情况

    2. 已取消

    3. 迭代器函数

    4. reduce函数

    callback的统一处理

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

    cb就是callback的简写,看函数的注释的意思是:内部函数,用来生成可应用于集合内每个元素的回调函数,返回预期的结果,具体应用向下看。

    iteratee什么鬼?

    _.iteratee = builtinIteratee = function(value, context) {
        return cb(value, context, Infinity);
      };

    结合上面的cb函数,貌似可以看到每次调用cb函数时都会判断一次_.iteratee是否等于builtinIteratee。

    如果不等于则调用_.iteratee函数,让_.iteratee = builtinIteratee,再继续执行cb函数。

    结合注释,猜测这个函数的作用应该是防止用户自己定义iteratee函数。

    restArgs又一个基础函数

    var restArgs = function(func, startIndex) {
        startIndex = startIndex == null ? func.length - 1 : +startIndex;
        return function() {
          var length = Math.max(arguments.length - startIndex, 0),
              rest = Array(length),
              index = 0;
          for (; 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);
        };
      };

    这个函数作用就类似ES6里面的rest params,这个函数主要是在官网分类里面的collections用到,例如:invoke。

    主要原理是利用回调函数来处理调用方法传入的参数。

    创建继承函数

      var baseCreate = function(prototype) {
        if (!_.isObject(prototype)) return {};
        if (nativeCreate) return nativeCreate(prototype);
        Ctor.prototype = prototype;
        var result = new Ctor;
        // 创建 result 之后清空 Ctor 的原型链,防止 全局变量 Ctor 的原型链污染
        Ctor.prototype = null;
        return result;
      };

    主要原理就是利用 Ctor 做一个中介,创建继承函数并返回后再清空Ctor的原型链,防止原型链污染

    取对象的属性值

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

    这个方法浅显易懂,如果传入的object为null,则返回 undefined,否则返回属性值。

    其它的全局变量

    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;
      };

    主要是帮助collection的方法来判定某个变量是否为collection。

    后记

    看完这一段,感觉依旧有许多疑问。主要是因为这些全局定义的变量的使用场景没有深究,更直白一些,就是没有按照代码的线索专研下去。希望在接下来的主要API的分析中能够在好好回顾上面的那些函数以及变量



沪ICP备19023445号-2号
友情链接