窥探Underscore源码系列-开篇

开篇说明

对的,如你所见,又开始造轮子了。哈哈,造轮子我们是认真的~

源码阅读是必须的,Underscore是因为刚刚学习整理了一波函数式编程,加上自己曾经没有太多阅读源码的经验,先拿Underscore练练手,跟着前辈们走一走,学一学。也相同时能够夯实js基础,从源码中学习到更多的编码技巧

Underscore源码阅读大致按照官方文档来编写.尽量的说明每一个函数的写法,希望自己可以从中可以收获大神的编码功力。

github:Personal_Blog

阅读目录

Underscore源码+注释地址

源码阅读

整体结构、变量介绍

(function(){}())

常规操作哈,跟jQuery一毛一样,通过IIFE来包裹业务逻辑,目的简单:1、避免全局污染。2、保护隐私

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

通过global和self来判断是node环境还是window环境,说白了,就是为了拿到全局变量。因为我们需要一个全局的变量,所以为了防止冲突,我们这里拿到root后,先暂存下之前的root.

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

  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(){};

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

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

  _.VERSION = '1.8.3';

由于Underscore本身依赖很多原生js的方法,所以这里为了避免原型链的查找性能消耗,Underscore通过局部变量来保存一些常用的对象和方法。既可以提升性能,减少对象成员访问深度也可以减少代码的冗长。

下面的Ctor和_ 是为了面向对象而准备的。

迭代

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

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

这里的迭代,我们需要理清楚一个概念,在Underscore中,我们需要改变那种命令式的编程方式,具体的可以看我之前写的关于函数式编程的文章哈。

所以这里想说的就是关于遍历迭代的东西。

var results = _.map([1,2,3],function(elem){
  return elem*2;
}); // => [2,4,6]

_.map = _.collect = function (obj, iteratee, context) {
    iteratee = cb(iteratee, context);
    var keys = !isArrayLike(obj) && _.keys(obj),
        length = (keys || obj).length,
        results = Array(length); // 定长初始化数组
    for (var index = 0; index < length; index++) {
        var currentKey = keys ? keys[index] : index;
        results[index] = iteratee(obj[currentKey], currentKey, obj);
    }
    return results;
};

我们传递给的 _.map 的第二个参数就是一个 iteratee,他可能是函数,对象,甚至是字符串。

  • value 为 null。则 iteratee 的行为只是返回当前迭代元素自身
  • value 为一个函数。那么通过内置函数 optimizeCb 对其进行优化
  • value 为一个对象。那么返回的 iteratee(_.matcher)的目的是想要知道当前被迭代元素是否匹配给定的这个对象
  • value 是字面量,如数字,字符串等。他指示了一个对象的属性 key,返回的 iteratee(_.property)将用来获得该属性对应的值

optimizeCb()

在上面的分析中,我们知道,当传入的 value 是一个函数时,value 还要经过一个叫 optimizeCb 的内置函数才能获得最终的 iteratee:

var cb = function (value, context, argCount) {
  // ...
  if (_.isFunction(value)) return optimizeCb(value, context, argCount);
  // ...
};

所以此处的optimizeCb必然是优化回调的作用了。


  // 优化回调的函数,遍历
  var optimizeCb = function(func, context, argCount) {
    // void 0 会返回真正的undefined 此处确保上下文的存在
    if (context === void 0) return func;
    switch (argCount == null ? 3 : argCount) {
      case 1: return function(value) {
        // argCount为0时候,迭代过程中,我们只需要这个value就可以了
        return func.call(context, value);
      };
      // The 2-parameter case has been omitted only because no current consumers
      //  3个参数(值,索引,被迭代集合对象).
      case 3: return function(value, index, collection) {
        return func.call(context, value, index, collection);
      };
      // 4个参数(累加器(比如reducer需要的), 值, 索引, 被迭代集合对象)
      case 4: return function(accumulator, value, index, collection) {
        return func.call(context, accumulator, value, index, collection);
      };
    }
    return function() {
      return func.apply(context, arguments);
    };
  };

整体的代码非常清晰,待优化的回调函数func,上下文context以及迭代回调需要的参数个数。

上面的这个优化的回调涉及到不同地方使用的不同迭代。这里暂时 先放一放。等过了一遍源码后,看到每一个用到迭代的地方,在回头来看,就会明白很多。

rest参数

在 ES6中,我们定义不定参方法的时候可以这么写

let a = (b,...c)=>{
console.log(b,c);
}

但是在此之前,Underscore实现了自己的reset,使用如下:

  function a(a,b,c,d,e){
      console.log(a,b,c,d,e)
  }
  let aa = restArgs(a);//let aa = restArgs(a,4)
  aa(1,2,3,4,5,6,7,8,8)

看下restArgs的实现:

var restArgs = function(func, startIndex) {
    //未传则取形参个数减一

    startIndex = startIndex == null ? func.length - 1 : +startIndex;

    return function() {
      //  多传了几个参数
      //length为多传了几个参数
      var length = Math.max(arguments.length - startIndex, 0),
          rest = Array(length),
          index = 0;
      for (; index < length; index++) {
        rest[index] = arguments[index + startIndex];
      }
      
      //优化。注意rest参数总是最后一个参数, 否则会有歧义
      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);
      }
      //撇去常用的startIndex,这里循环
      //先拿到前面参数
      var args = Array(startIndex + 1);
      for (index = 0; index < startIndex; index++) {
        args[index] = arguments[index];
      }
      //拿到后面的数组
      args[startIndex] = rest;
      return func.apply(this, args);
    };
  };  

面向对象

关于面向对象,这里不做过多解释了,可以参考我的另一篇文章:
javasript设计模式之面向对象

我们直接看他的继承实现吧

  var Ctor = function(){};
  
  // 定义了一个用于继承的内部方法
  var baseCreate = function(prototype) {
    if (!_.isObject(prototype)) return {};
    // nativeCreate = Object.create;
    if (nativeCreate) return nativeCreate(prototype);
    Ctor.prototype = prototype;
    var result = new Ctor;
    Ctor.prototype = null;
    return result;
  };

es5 中,我们有一种创建对象的方式,Object.create 。

function Animal(name){
  this.name = name;
}
Animal.prototype.eat = function(){
  console.log(this.name,'鸟为食亡');
}
var dog = Object.create(Animal.prototype);
dog.name = "毛毛";
dog.eat();

ok,大概从上大家就看出来create的作用了。

baseCrate中,首先判断是否为对象,否则退出。浏览器能力检测是否具备Object.create方法,具备则用。否则采用寄生式继承创建对象。需要注意的是,baseCreate仅仅支持原型继承,而不能像Object.create那样传递属性列表。

结束语

开篇简单的介绍Collection Functions上面的代码部分。在介绍Collection Function每个方法实现之前,我们将在下一篇看一下一些工具方法的编写方式。

的确在造造轮子,只是更想自己撸一遍优秀代码。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,185评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,652评论 3 393
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,524评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,339评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,387评论 6 391
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,287评论 1 301
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,130评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,985评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,420评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,617评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,779评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,477评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,088评论 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,716评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,857评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,876评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,700评论 2 354

推荐阅读更多精彩内容

  • 讲在前面 这次选的计划是源码阅读计划,因为之前花时间x重新再过了高程和权威,觉得得看一些实在的东西来提高自己。因此...
    临水照影233阅读 309评论 0 0
  • underscore 基础篇 这一部分,我们会介绍一些 underscore 中的基础内容,包括有 undersc...
    宫若石阅读 5,282评论 1 7
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,098评论 25 707
  • 1.今天真是收获满满的一天,白天有幸听到白晓鹤老师讲课培训,同时晚上麻烦代老师再次给我们明确工作任务。 2.感恩!...
    我不信除非你给我100块阅读 388评论 0 1
  • 话说有一个程序员叫小明,业务能力挺不错,人也不丑,但是一直没有女朋友,周围同事朋友们都很着急。 后来有一天小明一个...
    TemetNosce阅读 176评论 0 0