Underscore整体架构浅析

前言

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

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

常规调用

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

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


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

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


  1. var _ = {}; 
  2. _.each = function() { 
  3.   // ... 
  4. };  

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


  1. var _ = function(obj) { 
  2.   if (obj instanceof _) return obj; 
  3.   if (!(this instanceof _)) return new _(obj); 
  4.   this._wrapped = obj; 
  5. };  

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

OOP

Underscore 支持 OOP 形式的调用:


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

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

方法挂载

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

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

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

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


  1. // Add your own custom functions to the Underscore object. 
  2. // 可向 underscore 函数库扩展自己的方法 
  3. // obj 参数必须是一个对象(JavaScript 中一切皆对象) 
  4. // 且自己的方法定义在 obj 的属性上 
  5. // 如 obj.myFunc = function() {...} 
  6. // 形如 {myFunc: function(){}} 
  7. // 之后便可使用如下: _.myFunc(..) 或者 OOP _(..).myFunc(..) 
  8. _.mixin = function(obj) { 
  9.   // 遍历 obj 的 key,将方法挂载到 Underscore 上 
  10.   // 其实是将方法浅拷贝到 _.prototype 上 
  11.   _.each(_.functions(obj), function(name) { 
  12.     // 直接把方法挂载到 _[name] 上 
  13.     // 调用类似 _.myFunc([1, 2, 3], ..) 
  14.     var func = _[name] = obj[name]; 
  15.  
  16.     // 浅拷贝 
  17.     // 将 name 方法挂载到 _ 对象的原型链上,使之能 OOP 调用 
  18.     _.prototype[name] = function() { 
  19.       // 第一个参数 
  20.       var args = [this._wrapped]; 
  21.  
  22.       // arguments 为 name 方法需要的其他参数 
  23.       push.apply(args, arguments); 
  24.       // 执行 func 方法 
  25.       // 支持链式操作 
  26.       return result(this, func.apply(_, args)); 
  27.     }; 
  28.   }); 
  29. }; 
  30.  
  31. // Add all of the Underscore functions to the wrapper object. 
  32. // 将前面定义的 underscore 方法添加给包装过的对象 
  33. // 即添加到 _.prototype 中 
  34. // 使 underscore 支持面向对象形式的调用 
  35. _.mixin(_);  

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


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

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


  1. // Add all mutator Array functions to the wrapper. 
  2. // 将 Array 原型链上有的方法都添加到 underscore 中 
  3. _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { 
  4.   var method = ArrayProto[name]; 
  5.   _.prototype[name] = function() { 
  6.     var obj = this._wrapped; 
  7.     method.apply(obj, arguments); 
  8.  
  9.     if ((name === 'shift' || name === 'splice') && obj.length === 0) 
  10.       delete obj[0]; 
  11.  
  12.     // 支持链式操作 
  13.     return result(this, obj); 
  14.   }; 
  15. }); 
  16.  
  17. // Add all accessor Array functions to the wrapper. 
  18. // 添加 concat、join、slice 等数组原生方法给 Underscore 
  19. _.each(['concat', 'join', 'slice'], function(name) { 
  20.   var method = ArrayProto[name]; 
  21.   _.prototype[name] = function() { 
  22.     return result(this, method.apply(this._wrapped, arguments)); 
  23.   }; 
  24. });  

链式调用

Underscore 也支持链式调用:


  1. // 非 OOP 链式调用 
  2. _.chain([1, 2, 3]) 
  3.   .map(function(a) {return a * 2;}) 
  4.   .reverse() 
  5.   .value(); // [6, 4, 2] 
  6.  
  7. // OOP 链式调用 
  8. _([1, 2, 3]) 
  9.   .chain() 
  10.   .map(function(a){return a * 2;}) 
  11.   .first() 
  12.   .value(); // 2  

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

_.chain = function(obj) {


  1. _.chain = function(obj) { 
  2.   // 无论是否 OOP 调用,都会转为 OOP 形式 
  3.   // 并且给新的构造对象添加了一个 _chain 属性 
  4.   var instance = _(obj); 
  5.  
  6.   // 标记是否使用链式操作 
  7.   instance._chain = true; 
  8.  
  9.   // 返回 OOP 对象 
  10.   // 可以看到该 instance 对象除了多了个 _chain 属性 
  11.   // 其他的和直接 _(obj) 的结果一样 
  12.   return instance; 
  13. };  

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

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


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

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


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

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


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

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

小结

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

作者:韩子迟

来源:51CTO

时间: 2024-08-03 21:59:53

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

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

最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐进增强)优雅的处理能力以及 Ajax 等方面周到而强大的定制功能无不令人惊叹. 另外,阅读源码让我接触到了大量底层的知识.对原生JS .框架设计.代码优化有了全新的认识,接下来将会写一系列关于 jQuery 解析的文章. 网上已经有很多解读 jQuery 源码的文章了,作为系列开篇的第一篇,思前想去

网络相册开发(8)——Cairngorm架构浅析

Cairngorm是Adobe公司推荐的Flex架构. 关于它的文档少的可怜,我只发现了这两个: Cairngorm中文文档: http://download.csdn.net/download/tcx1986/513560 Developing Flex RIAs with Cairngorm microarchitecture http://www.adobe.com/devnet/flex/articles/cairngorm_pt1.html Cairngorm 架构包括六部分:Mode

OkHttp 3.7源码分析(一)——整体架构

OkHttp3.7源码分析文章列表如下: OkHttp源码分析--整体架构 OkHttp源码分析--拦截器 OkHttp源码分析--任务队列 OkHttp源码分析--缓存策略 OkHttp源码分析--多路复用 OkHttp是一个处理网络请求的开源项目,是Android端最火热的轻量级框架,由移动支付Square公司贡献用于替代HttpUrlConnection和Apache HttpClient.随着OkHttp的不断成熟,越来越多的Android开发者使用OkHttp作为网络框架. 之所以可以

Spring源码整体架构

前言 Spring 是一个开源框架,是为了解决企业应用程序开发复杂性而创建的.框架的主要优势之一就是其分层架构,分层架构允许您选择使用哪一个组件,同时为 J2EE 应用程序开发提供集成的框架. 从这篇文章开始,我讲开始阅读并介绍 Spring 源码的设计思想,希望能改对 Spring 框架有一个初步的全面的认识,并且学习其架构设计方面的一些理念和方法. Spring 源码地址:https://github.com/spring-projects/spring-framework 概述 Sprin

《Spring技术内幕》——1.3节Spring的整体架构

1.3 Spring的整体架构 了解了Spring的设计理念之后,我们继续介绍Spring的整体架构.在Spring中,我们大致按照一个参考关系,将其划分为几个层次,比如IoC容器.AOP核心模块.封装的Java EE服务.作为中间的驱动组件.其他作为上层的应用,这些应用不但包括来源于社区的应用封装,如ACEGI,也包括使用Spring作为平台开发出来的各种类型的企业应用. 从技术上看,Spring是封装得很清晰的一个分层架构,可以参考如图1-4所示的Spring架构图. 在这个架构图中,我们可

Spark技术内幕:Storage 模块整体架构

Storage模块负责了Spark计算过程中所有的存储,包括基于Disk的和基于Memory的.用户在实际编程中,面对的是RDD,可以将RDD的数据通过调用org.apache.spark.rdd.RDD#cache将数据持久化:持久化的动作都是由Storage模块完成的.包括Shuffle过程中的数据,也都是由Storage模块管理的.可以说,RDD实现了用户的逻辑,而Storage则管理了用户的数据.本章将讲解Storage模块的实现. 1.1     模块整体架构 org.apache.s

深入研究Clang(一)Clang和LLVM的关系及整体架构

作者:史宁宁(snsn1984) Clang和LLVM的关系         Clang和LLVM到底是什么关系,这是在研究Clang的过程中所不可避免的一个问题.如果要搞清楚Clang和LLVM之间的关系,首先先要知道宏观的LLVM和微观的LLVM.         宏观的LLVM,指的是整个的LLVM的框架,它肯定包含了Clang,因为Clang是LLVM的框架的一部分,是它的一个C/C++的前端.虽然这个前端占的比重比较大,但是它依然只是个前端,LLVM框架可以有很多个前端和很多个后端,只

一般“SAP的整体架构”指的是什么

问题描述 麻烦大家帮助解释下"SAP的整体架构"指的是什么? 解决方案 解决方案二:命题太大了吧,不过SAP的整体架构其实就是SAP解决方案三:看看下面这个链接可能对你会有用http://www.doc88.com/p-740682680544.html解决方案四:同求,第一次接触解决方案五:太泛泛了

深入理解bootstrap框架之第二章整体架构_jquery

一. 整体架构 1. CSS-12栅格系统 把网页宽度均分为12等分(保留15位精度)--这是bootstrap的核心功能. 2.基础布局组件 包括排版.按钮.表格.布局.表单等等. 3.jQuery bootstrap插件的基础 4.响应式设计 兼容多个终端.这是bootstrap的终极理念. 5.css插件 提供丰富的样式. 6.js插件 二. 栅格系统 1.基本实现过程 定义容器的大小--跳转边距--媒询 有以下要求: (1)一行(row)数据必须包含在.container中. .cont