大型JavaScript应用程序架构模式
11月中旬在伦敦举行的jQuery Summit顶级大会上有个session讲的是大型JavaScript应用程序架构,看完PPT以后觉得甚是不错,于是整理一下发给大家共勉。
PDF版的PPT下载地址:http://www.slideshare.net/jibyjohnc/jqquerysummit-largescale-javascript-application-architecture
注:在整理的过程中,发现作者有些思想是返来复去地说,所以删减了一部分,如果你的英文良好,请直接阅读英文的PPT。
以下是本文的主要章节:
1. 什么叫“JavaScript大型程序”?
2. 顾当前的程序架构
3. 长远考虑
4. 头脑风暴
5. 建议的架构
?? 5.1 设计模式
??????? 5.1.1 模块论
??????????? 5.1.1.1 综述
??????????? 5.1.1.2 Module模式
??????????? 5.1.1.3 对象自面量
??????????? 5.1.1.4 CommonJS模块
??????? 5.1.2 Facade模式
??????? 5.1.3 Mediator模式
??? 5.2 应用到你的架构
??????? 5.2.1 Facade - 核心抽象
??????? 5.2.2 Mediator - 程序核心
??????? 5.2.3 紧密联合运作起来
6. 发布Pub/订阅Sub的延伸:自动注册事件
7. Q & A
8. 致谢
什么叫“JavaScript大型程序”?在我们开始之前,我们来定义一下什么叫大型JavaScript站点,很多有经验的JS开发高手也都被challenge住了,有人说超过10万行JavaScript代码才算大型,也有人说JavaScript代码要超过1MB大小才算,其实2者都不能算对,因为不能安装代码量的多少来衡量,很多琐碎的JS代码很容易超过10万行的。
我对“大”的定义如下,虽然可能不太对,但是应该是比较接近了:
我们来头脑风暴一下,我们需要一个松耦合的架构,各模块之间没有依赖,各个模块和程序进行通信,然后中间层接管和处理反馈相应的消息。
例如,我们如果有一个JavaScript构建在线面包店程序,一个模块发出了一个信息可能是“有42个圆面包需要派件”。我们使用不同的layer层来处理模块发来的消息,做到如下:
这将防止我们因为某个模块出错,而导致所有的模块出错。
另外一个问题是安全,真实的情况是,大多数人都不认为内部安全是个问题,我们自己心里说,程序是我自己构建的,我知道哪些是公开的那些私有的,安全没问题,但你有没有办法去定义哪个模块才能权限访问程序核心?例如,有一个chat聊天模块,我不想让他调用admin模块,或者不想让它调用有DB写权限的模块,因为这之间存在很脆弱,很容易导致XSS攻击。每个模块不应该能做所有的事情,但是当前大多数架构里的JavaScript代码都有这种的问题。提供一个中间层来控制,哪个模块可以访问那个授权的部分,也就是说,该模块最多只能做到我们所授权的那部分。
我们本文的重点来了,这次我们提议的架构使用了我们都很熟知的设计模式:module, facade和mediator。
和传统的模型不一样的是,为了解耦各个模块,我们只让模块发布一些event事件,mediator模式可以负责从这些模块上订阅消息message,然后控制通知的response,facade模式用户限制各模块的权限。
以下是我们要注意讲解的部分:
??? 1 设计模式
??????? 1.1 模块论
??????????? 1.1.1 综述
??????????? 1.1.2 Module模式
??????????? 1.1.3 对象自面量
??????????? 1.1.4 CommonJS模块
??????? 1.2 Facade模式
??????? 1.3 Mediator模式
??? 2 应用到你的架构
??????? 2.1 Facade - 核心抽象
??????? 2.2 Mediator - 程序核心
??????? 2.3 紧密联合运作起来
大家可能都或多或少地使用了模块化的代码,模块是一个完整的强健程序架构的一部分,每个模块都是为了单独的目的为创建的,回到Gmail,我们来个例子,chat聊天模块看起来是个单独的一部分,其实它是有很多单独的子模块来构成,例如里面的表情模块其实就是单独的子模块,也被用到了发送邮件的窗口上。
另外一个是模块可以动态加载,删除和替换。
在JavaScript里,我们又几种方式来实现模块,大家熟知的是module模式和对象字面量,如果你已经熟悉这些,请忽略此小节,直接跳到CommonJS部分。
Module模式
module模式是一个比较流行的设计模式,它可以通过大括号封装私有的变量,方法,状态的,通过包装这些内容,一般全局的对象不能直接访问,在这个设计模式里,只返回一个API,其它的内容全部被封装成私有的了。
另外,这个模式和自执行的函数表达式比较相似,唯一的不同是module返回的是对象,而自执行函数表达式返回的是function。
众所周知, JavaScript不想其它语言一样有访问修饰符,不能为每个字段或者方法声明private,public修饰符,那这个模式我们是如何实现的呢?那就是return一个对象,里面包括一些公开的方法,这些方法有能力去调用内部的对象。
看一下,下面的代码,这段代码是一个自执行代码,声明里包括了一个全局的对象basketModule, basket数组是一个私有的,所以你的整个程序是不能访问这个私有数组的,同时我们return了一个对象,其内包含了3个方法(例如addItem,getItemCount,getTotal),这3个方法可以访问私有的basket数组。
想想一下,各模块是发布者,mediator既是发布者又是订阅者。
可以看到,各模块之间并没有通信,另外Mediator也可以实现监控各模块状态的功能,例如如果Module 3出错了,Mediator可以暂时只想其它模块,然后重启Module 3,然后继续执行。
回顾一下,可以看到,Mediator的优点是:松耦合的模块由同一的Mediator来控制,模块只需要广播和监听事件就可以了,而模块之间不需要直接联系,另外,一次信息的处理可以使用多个模块,也方便我们以后统一的添加新的模块到现有的控制逻辑里。
确定是:由于所有的模块直接都不能直接通信,所有相对来说,性能方面可能会有少许下降,但是我认为这是值得的。
?
我们根据上面的讲解来一个简单的Demo:
Facade抽象应用程序的核心,避免各个模块之间直接通信,它从各模块上订阅信息,也负责授权检测,确保每个模块有用自己单独的授权。

Mediator(应用程序核心)使用mediator模式扮演发布/订阅管理器的角色,负责模块管理以及启动/停止模块执行,可以动态加载以及重启有错误的模块。

这个架构的结果是:各模块之间没有依赖,因为松耦合的应用,它们可以很容易地被测试和维护,各模块可以很容易地在其它项目里被重用,也可以在不影响程序的情况下动态添加和删除。
关于自动注册事件,需要遵守一定的命名规范,比如如果一个模块发布了一个名字为messageUpdate的事件,那么所有带有messageUpdate方法的模块都会被自动执行。有好处也有利弊,具体实现方式,可以看我另外一篇帖子:jQuery自定义绑定的魔法升级版。
尽管架构的大纲里提出了facade可以实现授权检查的功能,其实完全可能由mediator去做,轻型架构要做的事情其实是几乎一样的,那就是解耦,确保各模块直接和应用程序核心通信是没问题的就行。
这其实就是一个两面性的问题,我们上面说到了,一个模块也许有一些子模块,或者基础模块,比如基本的DOM操作工具类等,在这个层面上讲,我们是可以用第三方类库的,但是请确保,我们可以很容易地能否替换掉他们。
我打算去搞一份代码样本供大家参考,不过在这之前,你可以参考Andrew Burgees的帖子Writing Modular JavaScript 。
技术上来将,没有理由现在模块不能和应用程序核心直接通信,但是对于大多数应用体验来说,还是不要。既然你选择了这个架构,那就要遵守该架构所定义的规则。