前言

终于,楼主的「Underscore 源码解读系列」underscore-analysis 即将进入尾声,关注下 timeline 会发现楼主最近加快了解读速度。十一月,多事之秋,最近好多事情搞的楼主心力憔悴,身心俱疲,也想尽快把这个系列完结掉,也好了却一件心事。

本文预计是解读系列的倒数第二篇,最后一篇那么显然就是大总结了。楼主的 Underscore 系列解读完整版地址 https://github.com/hanzichi/underscore-analysis

常规调用

之前写的文章,关注点大多在具体的方法,具体的知识细节,也有读者留言建议楼主讲讲整体架构,这是必须会讲的,只是楼主把它安排在了最后,也就是本文,因为楼主觉得不掌握整体架构对于具体方法的理解也是没有大的问题的。

Underscore 大多数时候的调用形式为 _.funcName(xx, xx),这也是 文档中 的调用方式。

_.each([1, 2, 3], alert);

最简单的实现方式,我们可以把 _ 看做一个简单的对象:

var _ = {};
_.each = function() {
  // ...
};

在 JavaScript 中,一切皆对象,实际上,源码中的 _ 变量是一个方法:

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

为什么会是方法?我们接下去看。

OOP

Underscore 支持 OOP 形式的调用:

_([1, 2, 3]).each(alert);

这其实是非常经典的「无 new 构造」,_ 其实就是一个 构造函数_([1, 2, 3]) 的结果就是一个对象实例,该实例有个 _wrapped 属性,属性值是 [1, 2, 3]。实例要调用 each 方法,其本身没有这个方法,那么应该来自原型链,也就是说 _.prototype 上应该有这个方法,那么,方法是如何挂载上去的呢?

方法挂载

现在我们已经明确以下两点:

  1. _ 是一个函数(支持无 new 调用的构造函数)
  2. _ 的属性有很多方法,比如 _.each_.template 等等

我们的目标是让 _ 的构造实例也能调用这些方法。仔细想想,其实也不难,我们可以遍历 _ 上的属性,如果属性值类型是函数,那么就将函数挂到 _ 的原型链上去。

源码中用来完成这件事的是 _.mixin 方法:

// Add your own custom functions to the Underscore object.
// 可向 underscore 函数库扩展自己的方法
// obj 参数必须是一个对象(JavaScript 中一切皆对象)
// 且自己的方法定义在 obj 的属性上
// 如 obj.myFunc = function() {...}
// 形如 {myFunc: function(){}}
// 之后便可使用如下: _.myFunc(..) 或者 OOP _(..).myFunc(..)
_.mixin = function(obj) {
  // 遍历 obj 的 key,将方法挂载到 Underscore 上
  // 其实是将方法浅拷贝到 _.prototype 上
  _.each(_.functions(obj), function(name) {
    // 直接把方法挂载到 _[name] 上
    // 调用类似 _.myFunc([1, 2, 3], ..)
    var func = _[name] = obj[name];

    // 浅拷贝
    // 将 name 方法挂载到 _ 对象的原型链上,使之能 OOP 调用
    _.prototype[name] = function() {
      // 第一个参数
      var args = [this._wrapped];

      // arguments 为 name 方法需要的其他参数
      push.apply(args, arguments);
      // 执行 func 方法
      // 支持链式操作
      return result(this, func.apply(_, args));
    };
  });
};

// Add all of the Underscore functions to the wrapper object.
// 将前面定义的 underscore 方法添加给包装过的对象
// 即添加到 _.prototype 中
// 使 underscore 支持面向对象形式的调用
_.mixin(_);

_.mixin 方法可以向 Underscore 库增加自己定义的方法:

_.mixin({
  capitalize: function(string) {
    return string.charAt(0).toUpperCase() + string.substring(1).toLowerCase();
  }
});
_("fabio").capitalize();
=> "Fabio"

同时,Underscore 也加入了一些 Array 原生的方法:

// Add all mutator Array functions to the wrapper.
// 将 Array 原型链上有的方法都添加到 underscore 中
_.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
  var method = ArrayProto[name];
  _.prototype[name] = function() {
    var obj = this._wrapped;
    method.apply(obj, arguments);

    if ((name === 'shift' || name === 'splice') && obj.length === 0)
      delete obj[0];

    // 支持链式操作
    return result(this, obj);
  };
});

// Add all accessor Array functions to the wrapper.
// 添加 concat、join、slice 等数组原生方法给 Underscore
_.each(['concat', 'join', 'slice'], function(name) {
  var method = ArrayProto[name];
  _.prototype[name] = function() {
    return result(this, method.apply(this._wrapped, arguments));
  };
});

链式调用

Underscore 也支持链式调用:

// 非 OOP 链式调用
_.chain([1, 2, 3])
  .map(function(a) {return a * 2;})
  .reverse()
  .value(); // [6, 4, 2]

// OOP 链式调用
_([1, 2, 3])
  .chain()
  .map(function(a){return a * 2;})
  .first()
  .value(); // 2

乍一看似乎有 OOP 和非 OOP 两种链式调用形式,其实只是一种,_.chain([1, 2, 3])_([1, 2, 3]).chain() 的结果是一样的。如何实现的?我们深入 chain 方法看下。

_.chain = function(obj) {
  // 无论是否 OOP 调用,都会转为 OOP 形式
  // 并且给新的构造对象添加了一个 _chain 属性
  var instance = _(obj);

  // 标记是否使用链式操作
  instance._chain = true;

  // 返回 OOP 对象
  // 可以看到该 instance 对象除了多了个 _chain 属性
  // 其他的和直接 _(obj) 的结果一样
  return instance;
};

我们看下 _.chain([1, 2, 3]) 的结果,将参数代入函数中,其实就是对参数进行无 new 构造,然后返回实例,只是实例多了个 _chain 属性,其他的和直接 _([1, 2, 3]) 一模一样。再来看 _([1, 2, 3]).chain()_([1, 2, 3]) 返回构造实例,该实例有 chain 方法,调用方法,为实例添加 _chain 属性,返回该实例对象。所以,这两者效果是一致的,结果都是转为了 OOP 的形式。

说了这么多,似乎还没讲到正题上,它是如何「链」下去的?我们以如下代码为例:

_([1, 2, 3])
  .chain()
  .map(function(a){return a * 2;})
  .first()
  .value(); // 2

当调用 map 方法的时候,实际上可能会有返回值。我们看下 _.mixin 源码:

// 执行 func 方法
// 支持链式操作
return result(this, func.apply(_, args));

result 是一个重要的内部帮助函数(Helper function ):

// Helper function to continue chaining intermediate results.
// 一个帮助方法(Helper function)
var result = function(instance, obj) {
  // 如果需要链式操作,则对 obj 运行 chain 方法,使得可以继续后续的链式操作
  // 如果不需要,直接返回 obj
  return instance._chain ? _(obj).chain() : obj;
};

如果需要链式操作(实例会有带有 _chain 属性),则对运算结果调用 chain 函数,使之可以继续链式调用。

小结

Underscore 整体架构,或者说是基础实现大概就是这个样子,代码部分就讲到这了,接下去系列解读最后一篇,讲讲这段时间(几乎也是历时半年了)的一些心得体会吧,没钱的就捧个人场吧!

Underscore 整体架构浅析的更多相关文章

  1. [数据库系列之MySQL] Mysql整体架构浅析一

    一.引言 平时我们在做Java系统时,一般情况下都会连接到一个MySQL数据库上去,执行各种增删改查的语句.大部分的Java工程师对MySQL的了解和掌握程度,大致就停留在这么一个阶段:对MySQL可 ...

  2. 【深入浅出jQuery】源码浅析--整体架构

    最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...

  3. 【深入浅出jQuery】源码浅析--整体架构(转)

    最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...

  4. 浅析MyBatis(一):由一个快速案例剖析MyBatis的整体架构与运行流程

    MyBatis 是轻量级的 Java 持久层中间件,完全基于 JDBC 实现持久化的数据访问,支持以 xml 和注解的形式进行配置,能灵活.简单地进行 SQL 映射,也提供了比 JDBC 更丰富的结果 ...

  5. jQuery 2.0.3 源码分析core - 整体架构

    拜读一个开源框架,最想学到的就是设计的思想和实现的技巧. 废话不多说,jquery这么多年了分析都写烂了,老早以前就拜读过, 不过这几年都是做移动端,一直御用zepto, 最近抽出点时间把jquery ...

  6. [转]Android App整体架构设计的思考

    1. 架构设计的目的 对程序进行架构设计的原因,归根到底是为了提高生产力.通过设计使程序模块化,做到模块内部的高聚合和模块之间的低耦合.这样做的好处是使得程序在开发的过程中,开发人员只需要专注于一点, ...

  7. jQuery整体架构源码解析(转载)

    jQuery整体架构源码解析 最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性, ...

  8. 《深入理解bootstrap》读书笔记:第二章 整体架构

    一.  整体架构   1. CSS-12栅格系统 把网页宽度均分为12等分(保留15位精度)--这是bootstrap的核心功能. 2.基础布局组件 包括排版.按钮.表格.布局.表单等等. 3.jQu ...

  9. Nginx的负载均衡 - 整体架构

    Nginx的负载均衡 - 整体架构 Nginx版本:1.9.1 我的博客:http://blog.csdn.net/zhangskd Nginx目前提供的负载均衡模块: ngx_http_upstre ...

随机推荐

  1. ASP.NET Core 中文文档 第二章 指南(4.8)添加新的字段

    原文 Adding a New Field 作者 Rick Anderson 翻译 谢炀(Kiler) 校对 许登洋(Seay).高嵩(Jack) 在这个章节你将使用 Entity Framework ...

  2. 解决MyEclipe出现An error has occurred,See error log for more details的错误

    今晚在卸载MyEclipse时出现An error has occurred,See error log for more details的错误,打开相应路径下的文件查看得如下: !SESSION 2 ...

  3. C#开发微信门户及应用(26)-公众号微信素材管理

    微信公众号最新修改了素材的管理模式,提供了两类素材的管理:临时素材和永久素材的管理,原先的素材管理就是临时素材管理,永久素材可以永久保留在微信服务器上,微信素材可以在上传后,进行图片文件或者图文消息的 ...

  4. Android 的进程和线程

    进程和线程 如果某个应用程序组件是第一次被启动,且这时应用程序也没有其他组件在运行,则android系统会为应用程序创建一个包含单个线程的linux进程.默认情况下,同一个应用程序的所有组件都运行在同 ...

  5. 通过Java代码实现对数据库的数据进行操作:增删改查

    在写代码之前,依然是引用mysql数据库的jar包文件:右键项目-构建路径-设置构建路径-库-添加外部JAR 在数据库中我们已经建立好一个表xs :分别有xuehao  xingming    xue ...

  6. 【工匠大道】 svn命令自己总结

     本文地址   分享提纲: 1. svn 不常见单有用的命令 2. svn查看切换用户 1. svn自己总结的一些不常见,但有用的命令 1)[导出svn不带版本代码]导出不带svn版本控制的代码到本地 ...

  7. Java输入输出常用类Scanner

    Scaner类,使用获取键盘输入. public boolean DemoTest(){ Scanner input = new Scanner(System.in); System.out.prin ...

  8. 地图中插入表格——ArcMap篇

    在制作专题图的过程中,不但要有地理要素表示空间位置,经常还要在图的周围制作一些表格数据.这里对ArcMap中的插入方法进行总结. 方法一:插入对象 利用菜单中的"插入"-" ...

  9. linux查看本机IP、gateway、DNS

    IP:     ifconfig gateway:[root@localhost ~]# netstat -rnKernel IP routing tableDestination     Gatew ...

  10. Critical: Update Your Windows Secure Channel (cve-2014-6321,MS14-066)

    前言:风雨欲来山满楼,下半年开始各种凶猛的漏洞层出不穷,天下已经不太平,互联网已经进入一个新的台阶 0x01 cve-2014-6321 11月的补丁月,微软请windows的用户吃了顿大餐,发布了1 ...