年糕阅读源码:Underscore.js(1)

前言

Underscore.js是一款精简但是对很多常用功能进行了封装的JavaScript框架(英文文档:http://underscorejs.org/,中文文档:http://www.css88.com/doc/underscore/),整个篇幅比较短,所以我觉得很适合我这种前端菜鸟来阅读吧。希望我能坚持阅读下去,并致力于弄懂。

一览

可以看到外面是直接调用匿名匿名函数,相当于function noName{...};noName();因为它会在里面定义到一些变量,而我们一般是用它内部定义的方法去访问,所以就会存在一个闭包。

(function() {
  // 内容
}())

满满的下缀_

可以看到代码中""这个符号出现的频率非常之高,可以查看中文文档,然后会发现它里面的方法前缀都是""来标记的。其实underscore这个名字的意思也是"_"。

预处理篇

定义根

下面我们来看代码,第一句是定义了root变量。root变量就是某个环境夏的最顶端变量,也是原型链的自顶端。在浏览器中是window,在node环境中是global,所以这里有四种判断情况。
联想提示:知道shim和polyfill的区别吗?
是为了能在低ES版本下实现高ES版本所以写的一系列代码,其实shim和polyfill都是这个意思,但是它们的区别是polyfill是特质浏览器中的shim,而shim则支持多个平台,会让我想到这个是因为这个定义也看得出underscore不仅支持浏览器,还支持Node环境。

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

Q:self是什么东西呀?
A:在浏览器中测试可知,这个self其实就是window。而self.self指向的也同样是window。

Q:为什么使用self.self和global.global能判断它就是window或者global呢?

保存会用到的变量方法

  //保存原来的下划线变量意味
  var previousUnderscore = root._;

  // 数组原型、对象原型、Symbol原型(如果支持Symbol)
  var ArrayProto = Array.prototype, ObjProto = Object.prototype;
  var SymbolProto = typeof Symbol !== 'undefined' ? Symbol.prototype : null;

  // 保存数组的push、slice、toString方法,保存Object的hasOwnProperty方法
  var push = ArrayProto.push,
      slice = ArrayProto.slice,
      toString = ObjProto.toString,
      hasOwnProperty = ObjProto.hasOwnProperty;

  // 在ES5中会使用到的方法:判断是否为数组,返回键值组成的数组,创建一个对象
  var nativeIsArray = Array.isArray,
      nativeKeys = Object.keys,
      nativeCreate = Object.create;

扩展回忆:
使用Object.create(原型),如果填入的参数为null,就可以创建一个没有原型的对象。

处理参数和函数

var Ctor = function(){};

定义一个空壳函数Ctor来方便做原型的替代

  var _ = function(obj) {
    if (obj instanceof _) return obj;
    if (!(this instanceof _)) return new _(obj);
    this._wrapped = obj;
  };

定义我们自己的_变量,是一个可以传入一个obj的函数。如果这个obj是的实例,就返回这个obj。如果不是的话就new一个(obj)返回,new操作会把新对象的原型指向_,this会指向新对象,并且会执行this.wrapped = obj。所以执行这个函数的话,会得到一个类似于下图的结构:

new _(obj)得到的实例结构

所以这个对象就成为了_的一个实例。

下面是对于在node环境中的处理此处还没找到实验环境,先跳过

  if (typeof exports != 'undefined' && !exports.nodeType) {
    if (typeof module != 'undefined' && !module.nodeType && module.exports) {
      exports = module.exports = _;
    }
    exports._ = _;
  } else {
    root._ = _;
  }

指明Undersocre.js的版本,这里可以看到我下载的源码是1.9.0版本的。

_.VERSION = '1.9.0';

处理参数

func:函数
context:上下文
argCount:参数个数
这里直接看其实也感觉很难理解,所以我会先在后面提取一个例子,结合文档的用法来搞清楚这个函数到底的作用。

  var optimizeCb = function(func, context, argCount) {
    if (context === void 0) return func;
   //如果没有传入argCount,进入3,否则对应进入不同的参数
    switch (argCount == null ? 3 : argCount) {
      // 返回一个直接传入value的函数
      case 1: return function(value) {
        return func.call(context, value);
      };
      // 两个参数的情况省略了,因为我们不会用到
      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);
    };
  };
  1. context === void 0是什么意思呀?

    关于void 0

    这是在stackOverflow上找到的一个回答,void 0可以和undefined有同样的作用,但是它的字节要少一些所以用了它来减少尺寸哈哈哈。

  2. 这里使用了argCount == null,argCount如果没有传入实际上是undefined,但是undefined == null,所以没有传入argCount会直接进行3的分支。

一个内置的函数来生成能调用多次调用回调函数:

  var builtinIteratee;
  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) && !_.isArray(value)) return _.matcher(value);
    return _.property(value);
  };

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

处理剩余传入的多个参数,化为一个数组,类似于ES6中的...rest:

  var restArguments = 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);
    };
  };

创建一个对象

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

返回对象的属性值(浅返回)

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

按照path数组,返回一组属性

  var deepGet = function(obj, path) {
    var length = path.length;
    for (var i = 0; i < length; i++) {
      if (obj == null) return void 0;
      obj = obj[path[i]];
    }
    return length ? obj : void 0;
  };

帮助集合来定义它是否为一个集合。应该以数组/对象的方式来循环吗?

 //数组索引最大值
  var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
//获取length值
  var getLength = shallowProperty('length');
//是不是类数组?
  var isArrayLike = function(collection) {
    var length = getLength(collection);
    return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
  };
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容