http://ued.sohu.com/article/611
首先,祝咱们所有搞前端开发的童鞋们新年快乐,同时祝大家新的一年在前端开发领域走的更高更远,都赚大钱!当然,如果想在年后换一个工作环境的同学,这里我们提供给大家一个加入搜狐 UED 和我们一起进步和提高的机会,详情点这里。
说正题了,相信大家经过两周的时间,状态都差不多调整和恢复过来了吧,年前的一小段时间,手头开发计划不是很多,就计划着把公司内部我们自己开发的一个JS库进行重构和完善,把它提升到框架的高度,而不简单的是一个工具箱,希望能提供给前端开发更多的指导方案,所以就花了点时间分析了一下现有常见的几个库,希望能帮助我们自己带来一些新的思路,我这边主要是对库中JS文件组织规划,类实现,接口使用模式三个方面,下面就先对Google Closure中的JS库部分,类实现做一个简单分析
文件结构图
说明: 下面我们先看看Google Closure的源文件物理结构图,有个比较直观的了解,后面会具体进一步分析,具体的类,命名空间和物理结构的关系
图例:
分析:
- goog 是顶级文件夹,它又包含了更多的子文件夹(如:timer、ui)
- 每个子文件夹下面一般都包含了具体的JS源文件(如:tooltip.js)
- 有时候子文件夹下面还包含了更下一级的文件夹(如ui下面的editor),而更下一级文件夹又包含了JS源文件
- 这里大部分深度是两层文件夹,第三层是JS源文件,而最大的深度是三层文件夹,第四层是JS源文件
- 总的来说,整个组织结构还是比较扁平简单的,没有太复杂的组织规划
一个比较完整的类实现实例(toolTip.js)
说明:这里是摘 自Google Cloure Lib中的一个ui类 Tooltip的实现,中间为了简化,把一些重复功能的语句给删减掉了,不影响我们下面对类实现细节的分析
代码:
- //Tooltip 类
- goog.provide(‘goog.ui.Tooltip‘);
- goog.provide(‘goog.ui.Tooltip.State‘);
- goog.require(‘goog.Timer‘);
- goog.require(‘goog.array‘);
- goog.ui.Tooltip = function(opt_el, opt_str, opt_domHelper) {
- this.dom_ = opt_domHelper || (opt_el ?
- goog.dom.getDomHelper(goog.dom.getElement(opt_el)) :
- goog.dom.getDomHelper());
- goog.ui.Popup.call(this, this.dom_.createDom(
- ‘div‘, {‘style‘: ‘position:absolute;display:none;‘}));
- };
- goog.inherits(goog.ui.Tooltip, goog.ui.Popup);
- goog.ui.Tooltip.activeInstances_ = [];
- goog.ui.Tooltip.prototype.className = goog.getCssName(‘goog-tooltip‘);
- goog.ui.Tooltip.prototype.attach = function(el) {
- el = goog.dom.getElement(el);
- this.elements_.add(el);
- goog.events.listen(el, goog.events.EventType.MOUSEOVER, this.handleMouseOver,false, this);
- };
分析: 一个JS源文件包含如下几个部分
- 自己能提供什么功能(类声明:使用goog.provide方法声明和注册一个类)
- 提供这些功能需要额外的哪功能的支持(依赖声明:使用goog.require方法声明具体依赖的其它类)
- 自身功能的具体实现(对象,类成员变量和成员方法声明和实现)
- GO ON: 下面我们继续详细的分解每一部分的实现方式和细节
命名空间构造和注册(自己能提供什么功能)
说明: 下面是命名空间申明,声明一个js文件能提供哪些类,provide方法就是根据字符串创建一个JS对象,俗称命名空间
代码:
- //Tooltip 类声明
- goog.provide(‘goog.ui.Tooltip‘);
- goog.provide(‘goog.ui.Tooltip.State‘); // Enum 形式的对象
- // ==========更多的例子============
- //[goog/array/array.js]
- goog.provide(‘goog.array‘);
- //[goog/dom/dom.js]
- goog.provide(‘goog.dom‘);// 命名空间声明,此命名空间下有变量和方法实现
- goog.provide(‘goog.dom.DomHelper‘);
- goog.provide(‘goog.dom.NodeType‘);
- //[goog/ui/combobox.js]
- goog.provide(‘goog.ui.ComboBox‘);
- goog.provide(‘goog.ui.ComboBoxItem‘); // Combobox的子项类
分析:
- 一个文件可以当成一个包
- 一个包中能声明(注册)多个类(多个类之间关系都比较密切[Tooltip 和 Tooltip.State],一般存在直接引用或者逻辑上是包含关系[Combobox和ComboboxItem],一般不超过三个)
- 结合最上面的文件物理结构,我们可以发现类的命名空间和文件夹结构基本是一一对应的,但却不是完全对应。(如:goog.ui.Tooptip类对应文件结构为goog/ui/tooltip.js,而goog.ui.Tooltip.State就不是了对应goog/ui/tooltip/state.js,所以不是完全对应)
- 根据第三点,下面会进一步分析google为什么没有直接利用这种对应关系以简化类到源文件的映射….
- GO ON: 声明完自己能提供什么功能后,下面接着我们需要收集实现这些功能,我们还可以充分利用已经存在和实现的哪些功能,所以就需要加载这些功能所对应的文件包。
直接依赖包加载(提供这些功能需要额外的哪功能的支持)
说明: 声明完成类或者对象后,这里就需要去加载该包直接依赖的其它包了,所谓直接依赖,就是指在代码实现中有直接引用类或者对象的,比如 goog.Timer.clear(this.showTimer),这里就需要使用goog.require(‘goog.Timer’)加载goog.Timer所在的文件包了。
代码 :
- //Tooltip 依赖包加载
- goog.require(‘goog.Timer‘);
- goog.require(‘goog.array‘);
- // 类,依赖和文件之间的关系实现
- goog.addDependency(‘ui/tooltip.js‘, [‘goog.ui.Tooltip‘,‘goog.ui.Tooltip.State‘], [‘goog.Timer‘, ‘goog.array‘, ‘goog.dom‘]);
- goog.addDependency(‘ui/tree/treenode.js‘, [‘goog.ui.tree.TreeNode‘],[‘goog.ui.tree.BaseNode‘]);
- goog.addDependency(‘useragent/useragent.js‘, [‘goog.userAgent‘],[‘goog.string‘]);
- 。。。。。。
分析:
- 这里理论上可以加载任意多个直接依赖的文件包
- 这里只是实现了包对包的依赖,并且这里使用的是异步加载机制,所以无法实现同步调用机制,即方法对包的依赖
- googleClosure 提供一张很大的表达来存储类及其类所依赖的其它类,以及该类对应的物理文件路径
- 再结合上面的物理文件结构和类声明部分,就大概知道为什么这里采用了非常庞大的一张表来存储类与JS源文件的映射关系,因为上面说的它的类名和文件夹结构不是完全对应的,如果完全对应的话,我们就不需要一张这么大的表来指明这种映射了,直接根据对应规则即可得到类的物理文件路径
- 当然,有一张映射表,物理文件结构会更灵活一些,比如这里的多个关系比较密切的类可以申明到一个文件中
构造函数实现
说明: 这里很简单,就是使用最原始也最符合js本身实现的一种方式声明一个类
代码:
- //Tooltip 构造函数
- goog.ui.Tooltip = function(opt_el, opt_str, opt_domHelper) {
- this.dom_ = opt_domHelper || (opt_el ?
- goog.dom.getDomHelper(goog.dom.getElement(opt_el)) :
- goog.dom.getDomHelper());
- goog.ui.Popup.call(this, this.dom_.createDom(
- ‘div‘, {‘style‘: ‘position:absolute;display:none;‘}));
- };
分析:
- 直接使用最原生的方式,简单直接明了,不从形式上去模仿Java后者其它后端语言,个人比较推荐
- 参数规范
- a.所有参数直接使用单个变量形式,不使用把可选参数放到一个options对象中
- b.针对可选参数,在前面opt_前缀,标识此参数是可选参数
- 使用call形式调用父类构造函数进行初始化,这里就能实现把父类实例对象的数据绑定到当前子类实例对象上,至于父类方法的绑定请继续看下面的继承声明和实现
继承声明和实现
说明: 上面是JS类构造函数的实现,其中有调用父类构造函数的实现,下面就如何继承父类进行分析,搞清楚这里就能明白父类方法绑定到子类的实现方式
代码:
- //Tooltip 继承实现
- goog.inherits(goog.ui.Tooltip, goog.ui.Popup);
- // 继承方法实现
- goog.inherits = function(childCtor, parentCtor) {
- /** @constructor */
- function tempCtor() {};
- tempCtor.prototype = parentCtor.prototype;
- childCtor.prototype = new tempCtor();
- childCtor.superClass_ = parentCtor.prototype;
- childCtor.prototype.constructor = childCtor;
- };
分析:
- 通过设置子类的prototype,引入一个含有空构造函数的tempCtor类,实现父类纯方法的继承
- 通过增加一个superClass变量指向父类的prototype,实现父类方法的直接调用,而不用担心子类重写了父类的方法,类似java的super调用
- 通过增加constructor变量指向子类自己,实现Js原生数据类型的 constructor 引用
- GO ON: 上面把一个包文件的结构部分基本分析清楚了,下面我们再继续分析一下一个类具体成员变量和成员方法的声明和实现的细节处理
属性声明
说明: 再接再励,继续分析类成员变量的声明和实现,这个基本比较简单,没什么难度,主要是需要说明一些书写规则和业内规范
代码:
- //Tooltip 成员变量
- goog.ui.Tooltip.activeInstances_ = [];
- goog.ui.Tooltip.prototype.className = goog.getCssName(‘goog-tooltip‘);
- // 更多
- goog.ui.PopupBase.DEBOUNCE_DELAY_MS = 150;
分析:
- 常量使用全大写,单词之间使用下划线分割
- 很少的公有变量
- 私有变量在后面添加下划线标识
- 使用getter和setter访问私有变量
- 没有保护权限的变量
方法实现
说明: 成员方法和成员变量基本一致
代码:
- //Tooltip 成员方法
- goog.ui.Tooltip.prototype.attach = function(el) {
- el = goog.dom.getElement(el);
- this.elements_.add(el);
- goog.events.listen(el, goog.events.EventType.MOUSEOVER, this.handleMouseOver,false, this);
- };
分析:
- 使用prototype声明方法
- 私有方法和私有变量保持一致,在后面添加下划线标识
- 公有方法直接声明
- 没有保护权限的方法
总结陈词
差不多了,这里只是一个非常简单的关于 Google Closure Lib中一个类组织和实现的一个大致分析,希望对各位同学在库这块有一些帮助,预告下先,后面会有关于 YUI3 的一个简单分析,敬请关注!