大型Javascript应用架构的模式(译文)

附上翻译好的word文件 https://files.cnblogs.com/lizhug/Patterns_For_Large-Scale_JavaScript_Application_Architecture.zip

 

作者:Addy Osmani   技术评审:Andree Hansson    翻译:李珠刚 @珠刚参上

 

今天我们将要探讨一系列用于大型Javascript应用架构的实用的模式。这个题材来源于我上次在LondonJs的同名演讲以及Nicholas Zakas先前的工作的一些启发。

 

我是谁?我为什么要说这个话题?

我现在是就职于AOL公司的Javascript和 UI工程师,主要设计和编写我们下一代面向客户的应用程序的前端架构。由于这些应用程序不仅复杂而且经常需要一个具有可扩展性和可复用的架构,确保应用于这些应用程序的模式尽可能的可持续是我的工作职责之一。

 

我把自己看作一个设计模式的狂热者(虽然对于这个话题有很多远比我知识丰富的专家)。我之前写了creative-common书本<essentail Javascript设计模式>,在写篇文章的的过程中我将同时引用那本书的一些细节。

 

你能在140字之内概括这篇文章么?

如果你的时间很紧,这里就有一个概括

 

去耦应用. 架构 w/模块,外观和中介者模式

 

 

什么是大型’Javascript应用

在我们开始之前,让我们尝试去定义一下Javascript应用程序中我们所理解的’大型’是什么。

我对于这个问题的答案是很主观的而且可能会与其他有多年经验的工程师有很大不同。

 

我询问了一些中级工程师他们对于’大型’的理解。一个工程师是这样说的:”超过十万行代码的Javascript程序” 同时另外一个说:”程序内部的Javascript代码有1M以上的程序”. 如果这些想法不是疯了的话,这两个说法应该都是错误的。

 

我自己的定义可能不会被大众接受,但是我相信这个定义可能接近了’大型’这个意思

 

在我看来,大型可扩展的Javascript应用程序是需要的工程师努力去实现的非凡的应用程序,需要处理大量的数据同时传递给浏览器。

 

定义的最后一句话是最意味深长的。

 

让我们回忆一下你现在掌握的架构

 

如果正在为一个有意义的大型Javascript应用程序工作,记得留出充分的时间去计划潜在的将会有很大意义的架构。它通常比你刚开始想象的更加复杂

 

我非常想要强调这一点的重要性-------我看到的一些靠近大型应用程序的工程师已经往回走了而且说:”好吧,这里有一系列的对于我上次一个中型的可扩展的项目有良好帮助的想法和模式,他们理所当然可以应用于再大一点的应用程序,不对么?” 尽管这在一定程序上是正确的,但是请不要想当然-------更大的应用通常有更多的需要考虑的因素。我将简短讨论一下为什么要花时间设计你的应用程序结构从长远来看是值得的。

 

大量的Javascript工程师可能在他们现在的架构中使用混合方式组合下列的一些结构

1、自定义部件 custom widget

2、模型 model

3、视图 views

4、控制器  controllers

5、模板  templates

6、库/工具包    libraries/toolkits

7、应用程序核心   an application core

 

相关阅读

Rebecca Murphey - Structuring JavaScript Applications

Peter Michaux - MVC Architecture For JavaScript Applications

StackOverflow - A discussion on modern MVC frameworks

Doug Neiner - Stateful Plugins and the Widget Factory

 

你可能也会分解你的应用程序函数,使他们成为一个个的模块,或者应用其他的模式。

这是值得鼓励的,但是如果这是你应用的全部架构的话,这里也会有一些你可能遇到的潜在问题。

 

1、你的架构有多少可以被立即重新实用的?

单一的一个模块可以独立存在么?他们是自包含的么?如果我现在看你们团队正在工作的大型应用的代码库然后随机选择一个模块,他可以嵌入一个新的页面然后开始使用么?你可能在做之前会对这个方法产生质疑,然而我还是鼓励你去考虑一下未来。如果你的公司开始去构架越来越多出色的应用哪些函数能够复用呢?如果有人说:”我们的用户喜欢在邮件客户端使用聊天模块,让我们把这个模块嵌入我们新的编辑套件里面吧” 那么有没有可能不经过深入修改就能使用这个模块呢?

 

2、在你的系统里有多少模块是需要依赖其他模块的呢?

他们是紧密耦合的么?在我探讨这个概念之前我需要申明 我理解在一个系统中模块之前绝对的不相互依赖是不大可能做到的。在粒度级别上你可能有模块需要依赖其他模块的功能,但是这个问题在功能模块群之间是更加有直接关系的。这些一系列在你的应用里直接相关不依赖太多其他模块或者函数是否运行或者被装载好是可能的。

 

3、如果你的应用里的特殊部分瘫痪了,应用还能运行么?

如果你正在构建一个类似Gmail的应用,你的网络邮件模块奔溃了,这不应该造成阻塞UI或者阻止用户使用应用的其它功能 比如聊天功能。同时,按照之前的,模块在理论上应该能够自动退出这个应用架构。在我的讲话里我提到了基于表达用户意图的动态模块。例如,在Gmail的例子里当核心模块初始化的时候聊天模块并不会被启动。以后当用户传递一个信息去使用聊天模块,他才会被动态装载。理论上来说,你希望他不会对应用的其他部分造成负面影响。

 

4、单独检测你的一个模块有多容易?

当测试一个具有良好扩展性的系统,这个系统可能需要满足潜在的上千万的用户去使用不同的模块,模块被重用于不同的应用是非常有必要的。当模块被初始化的时候,不管在架构的内部或者外部都需要满足测试的要求。在我看来,这有这样这个模块用于其他系统也不会出问题的观点才有说服力。

 

目光长远

当你设计大型应用程序的架构的时候,往前多想一步是很有必要的。不仅仅是一个月或者一年后,我们要考虑更加长远。什么会改变呢?去猜想你的应用会变成什么样当然是不可行的,但是这里有几个方面值得考虑的。在这里,在你的应用里至少有一个特殊的层面会出现在你的大脑里。

 

工程师经常把他们的DOM操作的代码跟应用的其他部分很紧密的耦合在一起,甚至当他们不辞劳苦的去把他们的核心逻辑分拆成模块的时候也会这样做。你看看….从远来看这难道不是一个好建议么?

 

有读者可能会说,现在所定义的严格地架构并不一定适用于以后。这个观点是正确的,但是如果没有更多的因素考虑在内的话,有另外一种概念可能更加有用

 

出于表现性、安全性或者未来的设计性,我们可以从Dojo, jQuery,Zepto 或者 YUI之中选择一些完全不同的东西。这会造成一个问题,因为库不是可以那么简易的互相交换的。如果这个库在你的应用中被应用的很普遍,那么库的替换将会造成很大的性能问题。

 

如果你是一个Dojo工程师(就像我讲话里的一些读者),你可能没有更好的想法去改变你所应用的库,但是谁能说2-3年后不会有你想要的更好的库出来呢?

 

在一个小的代码库里面这可能一个相对来说不是太重要的抉择,但是对于大的应用,有一个能够灵活的不用太介意在你的模块里用了什么样的库,从经济以及节约时间的角度来看,将会产生极大的益处。

 

总的来说,如果你现在回顾一下你的架构,在不完全重写你的应用的基础上你能够切换你使用的库么?如果不能,那就要考虑继续阅读这篇文章,因为我觉得今天列出的一些架构将会很有趣。

 

有一些很有影响力的Javascript工程师已经说了一些我今天提到的一些概念。这边有三个观点我提供分享。

 

“构建大型应用程序的秘诀就是永远不要去构建大型应用程序,把你的应用拆分成小的块,然后把那些通过测试的,小的块集成到你的应用里面去”----Justin Meyer, author JavascriptMav

 

“秘诀就是你要承认在刚开始的时候你对于这个程序会发展成什么样没有任何想法。当你接受了你什么都不知道,你开始去防御性的去设计这个系统,你能识别出可能改变的简单的需要你花费一些时间的关键点。举个例子,你应该希望应用里面与其他系统交互的任何部分都能改变,所以你需要把他们抽象化”---Nicholas Zakas, author ‘High-performance Javascript Website’

 

最后一个但同等重要的

 

“把组件互相绑得越紧,复用性就会越低,而且当没有改变影响他的其他组建的时候更改组建将会很麻烦”  ----Rebecca Murphey,author of jQuery Fundamentals

 

这些原则在构建可以经受时间考验的架构的时候是很重要的,并且需要一直在你的脑海里。

 

头脑风暴

让我们想想我们一会儿将要去实现什么。

 

我想要有一个把函数装进不互相依赖的独立的模块的松耦合的架构。 当发生了一些有趣的事,这些模块将会向应用的其他模块传递信息,中间层将会解释以及对这个信息进行解释

 

举个例子,假设我们拥有一个用于在线面包店的应用,有一条从模块中发出”有趣的”信息可能是”有一批42个面包卷准备好被发配”

 

我们使用不同的层从模块里去解释这些消息

a) 模块不会直接获取这些要点

b) 模块不需要直接调用或者互相影响,这一点保证了当特殊模块产生错误的时候该模块不会崩溃同时提供给我们重新启动已经奔溃的模块的方式

 

另外一个概念就是安全性。事实是我们当中很多的人都不把应用内部的安全当作一个概念。我告诉自己当我们搭建应用的时候,我们有足够的能力去分辨公有和私有的部分。

 

然而,如果你有办法来确定哪些模块是系统中允许的,他是不是不会提供帮助?比如,假设我知道我在系统中没有权限用公共聊天组件与一个管理员模块或者数据库写的模块连接,通过一些XSS,我可以限制有人利用我已经在组件中发现的漏洞的机会。模块不应该去使用任何东西。他们可能可以 在大多数现在的架构中,但是他们真的需要么?

 

拥有一个中间层操控什么模块可以操作你的框架的哪个部分将会增加你系统的安全性。这意味着模块只能做他们被允许做的事情。

 

 

 

可行的架构

我们想要去定义的一个架构集合了下面三个著名的设计模式:模块,外观,中介者。

 

相比较于模块之间直接互相沟通的模型,在这个解耦的架构中,他们将用有趣的东西(理想的说,不知道系统的其他模块)替代发布事件。 中介者模式将被用来想这些模块发布信息,也将操控什么样的返回信息适合作为响应。 外观模式将会被用来模块之间的授权。

 

我下面将介绍这些模式的细节(注:其实就是下面将要讲的目录)

 

1 设计模式

         模块理论

                   高度的概括性

                   模块模式

                   对象字面量

                   CommonJs模块

         外观模式

         中介者模式

2 应用到你的架构中

         外观模式 – 抽象的核心

         中介者模式 – 应用的核心

         把他们混合起来

 

 

模块理论

你可能已经在你现有的架构中应用了一些模块了。但是如果你没有的话,这边将给你一个将蛋的介绍

 

在一个健壮应用架构里面、模块是一个完整的小块而且在一个大的了可相互交换的系统中只有一个典型的目的。

 

取决于你怎么实现你的模块,定义可被一起立即自动装载的模块之间的依赖性是可行的。相比起必须去追踪他们之间的大量依赖以及手动装入模块或者引入脚本标签,这个方式是更加有扩展性的。

 

 

任何非凡的应用都应该从模块化的接口基础上建立。 再回到Gmail这个例子中,你应该把模块看成一个独立存在的功能单元,例子中的聊天模块就是其中之一。他可能有聊天模块,然而,他有多少个子单元,取决于你的功能单元有多复杂。举个例子,有一个简单的功能可能去操控那些表情可以在聊天和邮件接口中共用。

 

在架构的讨论中 ,模块对于系统中其他模块的了解是非常少的。所以,我们通过外观模式把这个职责给中介者。

这是靠设计的因为如果一个模块只负责让系统知道什么时候不需要担心其他模块是否在运行的有趣的事情发生了,一个系统应该满足在紧耦合的设计中支持在不让其他模块停止的情况下,增加、删除或者替换该模块。

 

因此如果这个想法实现的话,松耦合是很重要的。它促进了模块通过移除代码可以实现更简易的维护。在我们的例子中,为了让功能变的正确,模块不应该依赖其他模块。当松耦合被很好的利用其来了的话,改变系统的一个部分将会如何影响其他部分是很容易可以预见的。

 

在Javascript中,在实现模块的时候有许多不同的实现模式,这里就包括比较有名的 模块模式和字面量。有经验的工程师已经对这个熟悉了,如果是这样的话,那就直接去看commonJs模块吧

 

模块模式

 

模块模式是一个可以很好的把隐私模块,状态,结构用闭包封装起来的设计模式。它提供了方法把公有的和私有的方法变量包装起来,防治部分代码段进入全局作用域造成跟其他工程师的接口冲突。用了这个模式,只有一个公有的API会返回,其他的都会被封装在闭包之内。

 

对于保护逻辑去处理繁重的数据处理提供了一个很简洁的方案同时只要暴露了一个接口给程序的其他部分去使用。这个模式跟立即函数(IIFE)的表达很像,除了返回的是一个对象而不是一个函数。

 

有一点需要注意,javascript中并没有真正的私有的概念,因为跟传统的语言不同,他并没有访问修饰符。变量既不能被显示的定义成私有或者共有,所以我们使用函数作用域去模拟这个概念。在模块模式里面,因为有了闭包,变量或者方法可以只能在函数中被获取。然后在返回的对象的中的变量或者方法任何人都可以获得。

 

下面你可以看到一个使用模块模式实现了的购物篮的例子。这个模块在全局对象中本身是完全自包含的,叫做basketModule。模块里的Basket数组保证了私有性所以你的应用的其他部分不能直接去读取他们。它保证了只能通过你所定义的作用域中的函数才能获取basket的值。

 

 

 

在这个模块里面,你会注意到我们返回了一个对象。他被basketModule自动获取,所以你才能像下面这样调用

 

 

 

 

上面的方法很好地在basketModule中构建了一个命名空间

 

从历史的角度来看,模块模式是有很多人包括Richard Cornford在2003年建立起来的,

随后它被Douglas Crockford在他的演讲中被普及话,然后 Eric Miraglia在YUI的博客中被重新介绍了一下。

 

 

模块模式是怎样在特殊的工具包或者框架中表现的呢?

 

Dojo

 

Dojo通过dojo.declare提供可以通过实现模块模式的用于其他东西中的跟类类似的功能。举个例子,如果我们想要定义store命名空间中的basket这个模块,它可以这样实现

 

 

混入dojo.provide的话这个会变的很强大

 

YUI

 

下面这个例子很大程度上基于Eric Miraglia实现的原始的YUI 模块模式、相对来说还是很容易理解的

 

 

 

jQuery

 

把jquery的代码被包含在模块模式中有很多种方式。如果模块之间有一些公用的部分,Ben Cherry 建议在模块中使用函数包装器。

 

下面的例子,一个定义新库和自动初始化的库函数在document.ready中被定义了

 

 

 

相关阅读

Ben Cherry - The Module Pattern In-Depth
John Hann - The Future Is Modules, Not Frameworks
Nathan Smith - A Module pattern aliased window and document gist
David Litmark - An Introduction To The Revealing Module Pattern

 

 

对象字面量

 

在对象的字面量中,一个对象被描述成一系列的用逗号分隔、用 {} 包含的键值对,

 

对象中的键可以是字符串也可以是跟随在冒号后面的标识符。在最后一个键值对后面不应该有逗号出现不然会出现错误

 

对象字面化不要求使用new实例化,但是不允许在申明的时候就使用括号里面的东西。下面你将会看到一个使用字面量语法的例子。新的成员可能被加进对象里面就像myModule.property = 'someValue';

 

尽管模块模式对于很多东西都是很有用的,但是如果你发现你自己不需要特殊的值或者方法需要变成私有的,对象字面化将会更加适合你

 

 

 

相关阅读

Rebecca Murphey - Using Objects To Organize Your Code
Stoyan Stefanov - 3 Ways To Define A JavaScript Class 
Ben Alman - Clarifications On Object Literals (There's no such thing as a JSON Object)
John Resig - Simple JavaScript Inheritance

 

 

CommonJS模式

在过去两年里,你可能听说过了commonJS 一个设计规范和标准化javascript API的志愿组织。迄今为止他们已经批准了模块和包的标准。CommonJS AMD提议指定了把script同步和异步加载如浏览器的简单的API。他们的模块模式相对来说是干净的而且我觉得这是JS迈向下一个时代的台阶。

 

从结构化的角度,CommonJS的模块是一个可重用的一段JavaScript给特定对象提供任何相关的代码。这个模块的标准越来越普遍的变成标准模块标准。有一些非常出色的教程来教授实现CommonJS模块 但是总的来说,他们基本包含两个基本的部分:一个需要引入其他对象的exports对象、一个引用其他模块的reqiure函数。

 

 

 

 

有许多出色的javascript库可以像commonJS一样引入其他js,我个人使用的就是RequireJS。完整的requireJS我就不能在这个教程中介绍了,但是我推荐阅读James Burke的东西,我知道一些人也像Yabble(没有这个单词呀)

 

除了这个, RequireJS提供了我们怎么样去延迟创建有包装器以及可以异步加载的工艺模块的静态模块。他可以简单的引入模块,他们依赖于他们的方式,如果有模块的话他们就会运行。

 

有许多工程师批评commonJS模块对于浏览器不够适合。理由是他们如果没有服务端的帮助的话并不能通过script引入。我们能想象一个用ascii去编码照片的的库可能会引入一个encodeToASCII函数,一个模块可能类似这样。

 

 

 

这种类型的方案用script标签将不会工作,因为作用域没有被包含,就是说encodeToASCII方法没有被绑定到window对象中,require将不会这样定义,对于每个模块exprots将需要分别地被创建。一个需要服务器端支持的客户端的库或者一个需要利用eval() 的XHR请求的script能很简易的操作。

 

使用RequireJS,模块可以被更加简易的重写。

 

 

对于不需要在项目里面依赖使用静态Javascript的工程师,CommonJS的模块是是一条出色的路,但是确实需要花费一些时间去研究它。我现在才涉及到冰山一角,但是CommonJS和Sitepen有一系列的资源去学习

 

相关阅读

The CommonJS Module Specifications
Alex Young - Demystifying CommonJS Modules
Notes on CommonJS modules with RequireJS

 

外观模式

 

接下来,我们将来看一下外观模式,一个在如今的架构中出于关键地位的模式

 

当你提到外观,你经常创建一个隐藏真实性的外表。外观模式为了对于提供了一个便捷的高层接口,隐藏了底层的复杂性。可以把他现象成为其他工程师提供的简化接口。

 

外观是经常可以在javascript库和框架中看见的结构化的模式,虽然他可能支持大量的行为的方法,但是一个’外观’抽象成了一个让客户端去使用。

 

这个方面了我们去跟外观交互而不是去跟后台的子程序交互。

 

外观很棒的理由就是他隐藏了在一个独立模块中功能本身特殊实现的细节。模块的实现可以在客户端不知道的情况下改变。

 

依靠维持一个一致的外观,模块内部是不是使用了dojo,jquery,YUI,zepto或者其他东西是不重要的。只要接口没有改变,在不影响系统其他部分的前提下,你保持了去切换库的能力

 

下面是一个关于外观的非常基础的例子。就像你看到的,我们的模块包含了一系列的私有的方法,外观提供了大量的简单API去使用这些方法。

 

 

 

这个就是我们把外观应用到我们的架构前的样子。接下来我们投入到令人振奋的中介者模式。

中介者模式和外观模式最核心的不同点是 外观(结构化的模式)仅仅暴露已经存在的功能然而中介者模式(行为化的模式)能够动态的添加功能

 

相关阅读

Dustin Diaz, Ross Harmes - Pro JavaScript Design Patterns (Chapter 10, available to read on Google Books)

 

中介者模式mediator pattern

 

用一个类比来介绍中介者模式可能更加好------你想想看我们典型的飞机场的操控,控制塔控制什么飞机能够起飞什么飞机能够下降,因为所以的信息交流都是从飞机和控制塔之间的,而不是飞机之间互相传递起飞降落的这些信息----(个人注:这个例子很好)有一个中心的控制对于这个系统的成功运行有良好的意义,而这个就是中介者模式所做的。

 

当模块之间的交流变的很复杂的时候中介者模式就很有用了,但仍然是意义明确的。如果在你系统的代码模块之间表现出很多的相关性,那么现在就该设置一个控制中心了。

 

在现实生活中,中介者把不同模块的交互通过媒介压缩在了一起。这个模式通过防治对象之间的相互依赖达到了良好的松耦合性-----在系统中,这帮助我们解决了模块依赖性问题。

 

他还提供了什么优点呢?中介者允许模块操作去改变独立性,所以这个是非常灵活的。如果你之前已经用过了观察者模式(PUB/SUB)模式去实现两个模块之间的广播,你会发现中介者模式是如此的容易理解。

 

让我们看看从高层看中介者中模块是如何交互的

 

 

 

把模块当作发布者,把中介者当作发布者和订阅者。模块1 广播了一个事件提醒中介者需要去做,中介者捕获这条信息然后开启需要去处理这条信息的模块2、告诉他模块1需要你,然后处理完车过后模块2发送一条广播信息给中介者,同时模块3被中介者启动去记录同志的结果。

 

注意,如何在模块之间没有直接交流的点。如果在这条链中的模块3失败了或者错误停止了,中介者可以假设暂停了其他模块上的任务,停止和重启模块3,然后在对系统影响很小的情况下继续工作。这种程度的解耦的一个主要优势是提供的模式。

 

回顾一下中介者的优点:

 

他通过有一个中心的控制点防止了模块的相互直接依赖。这个只会造成一个很小的性能下降。

-----因为松耦合的自然性,只通过观察广播中的信息是无法知道系统是如何响应的。最后,紧耦合是令人头疼的主要原因、下面就是一个解法;

 

例子:这是一个可行的中介者模式的实现 by @rpflorence

 

 

 

 

 

这是上面的两个具体应用,他很好的控制了发布者和订阅者

 

 

相关阅读

Stoyan Stefanov - Page 168, JavaScript Patterns
HB Stone - JavaScript Design Patterns: Mediator
Vince Huston - The Mediator Pattern (not specific to JavaScript, but a concise)

 

外观模式的应用:抽象的核心

在架构中的建议

外观作为一个把应用核心抽象的角色,充当了中介者和我们的模块之间的角色。它是唯一的系统的其他模块能够了解的一个部分。

 

这个抽像者的职责包括,保证提供给这些模块的接口是稳定的。他比较类似于Nicholas Zakas首先提出来的沙箱中的控制者

 

组件通过外观与中介者进行交互,因此他需要变得很可靠。当我说到”交流”,我需要解释的一点是 外观就像是中介者的一个抽象表示,他将会去监听从模块中传递过来的信息然后传递给中介者。

 

除了提供接口给模块外,外观也表现为一个警卫,决定了应用的那个模块可以从他这里获取数据。组件仅仅只能调用他们自身的方法而不可以做他们不被允许做的事情。举个例子,一个模块或许会发出这样的信息dataValidationCompletedWriteToDB  安全检查的保证就是模块已经被授权了请求数据库写入。我们理论上希望做到避免模块意外的去做了他们不被许可的事情。

 

简短的回顾,中介者保留了观察者模式的控制方式,但是他们只做外观同意他们做的事情。

 

中介者模式的应用:应用的核心

 

中介者充当了应用核心的角色,我们已经简短的了解了一些他的职责,现在我们把他们的职责都介绍一下。

 

这个核心最基本的任务时控制模块的生命周期。当核心检测到一个消息,它将决定什么模块将被启动 --- 他意味着决定了那个模块或者哪个系列的模块需要被启动或者停止。

 

一旦一个模块被开启,他理论上需要自动启动。关于什么时候DOM已经就绪或者对于模块来说是否足够的作用域这不是核心所需要关心的事

 

你可能对一个模块什么情况下该被停止感到奇怪。如果一个应用探测到一个特殊的模块已经失效或者传递出了错误的信息,核心就会做出一个决定防治这个模块继续运行下去,而是去重启他。这里的目标是去帮助降低用户的破坏性。

 

除此之外,核心应该可以在不影响其他部分的情况下移除或者获取模块。一个典型的例子是某个模块中的一个功能可能在初始的页面中不需要了。但是根据用户的操作去动态的加载。

让我们返回到Gmail这个例子中,google可以使聊天模块动态加载当用户需要使用这个功能的时候。从表现最优的角度来看,这个一个非常有意义的做法。

 

错误的管理也将由应用核心来操作。除了广播模块之间的消息,他们也会广播任何错误的信息,然后根据具体的错误信息去处理。在解耦的架构中这去拥有足够的范围去操作新的或者更好的方法或者报错是非常重要的。使用通过中介者使用观察者的方法允许了我们去做这个。

 

把他们都一起来讲一遍

1、模块包含了你应用里的特殊的功能块。他们通知系统什么时候有一个他们感兴趣的事件发生了。我将会在下面的问答中解释,模块依赖于DOM工具方法,但是理论上不能依赖系统中的其他模块,他们不应该被这样理解:

         ① 哪些对象或者模型订阅了他们发布的信息

         ② 这些对象基于什么(服务器端或者客户端)

         ③ 多少对象订阅了通知

 

 

 

2、外观抽象了核心是为了防止模块直接去跟他交互。他订阅了他感兴趣的事件并且说”非常好!发生什么事了?”他同样通过检测模块是否拥有必要的权限控制着模块的安全性。

 

 

 

3、中介者(应用的核心)通过中介者模式充当了一个”发布/订阅”管理者的角色。它主要负责模块的管理、启动停止模块。在动态加载中以及保证模块能够被重启中被特别的应用了。

 

 

 

 

超越发布/订阅:自动的事件注册

 

就像 Michael Mahemoff 先前提到的,当考虑大型的可扩展性的Javascript的时候,开发语言中有类似动态语言特征是很有好处的。你可以在Michael Mahemoff的G+博客中获得更多的关于这一方面的概念,但我推荐你去看一点 ----动态事件注册(AER)

 

AER通过名传递,实现了发布者和订阅者的自动连接。举个例子,如果一个模块发出了一个事件叫做messageUpdate 任何有messageUpdate 这个方法的都将被调用。

 

这个模式的建立涉及了所有订阅了这个方法的接口,注册了所有的订阅的事件,以及所有接口集中的子事件,并把事件跟他绑定起来。这是一个构架中的非常好的途径,但是可能会遇到一些挑战。

 

举个例子,当动态运行的时候,对象可能需要在运行之前就绑定完毕。请去看一下Michael公布的怎样去深层的操作这些东西。

 

常见的问题和解答

Q:是不是可以避免实现沙箱或者外观

A:虽然在架构的概述中使用了外观去实现安全的要求,但是他们完全可以只用一个中介者以及发布和订阅去实现事件在应用之中传递。所有的版本都提供一个类似的解耦,但是请保证你在操作模块之间直接操作的时候是很得心应手的。

 

Q:你提到了模块没有任何依赖,这是不是意味着它不依赖第三方的库?(比如jQuery)

A:我将再提一遍依赖于其他模块的这个概念。一些工程师选择一个类似架构的时候通常选择他的DOM的抽象工具库。例如一个人可能使用jquery查询的DOM并且返回结果的库(也可以是Dojo或者其他)用这个方法,虽然所有的模块都查询了DOM,但是他们没有直接使用库或者工具包里面硬编码的函数。关于怎么去实现它有很多不同的方法,但是理论上构架的时候核心的模块不应该依赖于其他模块。

 

你将会发现在这个例子中 有些时候从其他工程中拿一个模块放到另外一个工程中是很容易的。我应该申明一点我完全同意有些时候把模块里面的一部分功能移到或者扩展到另外一个模块可能是有点困难的,但是你需要有这个概念,就是他将会在其他模块中很好的适应。

 

Q:我今天开始就将使用架构。有没有什么样板代码让我去学习的?

A:如果时间允许我将发出这部分的代码,但是同时,Android Burgee(完全的开源,我再介绍给别人的时候也再一次的去回顾了一下)的”writing Modular Javascript”或许是一个很好的选择。Andrew的包 包括了包括了截图代码以及今天介绍内容里面的绝大部分内容。就像我回答的第二个问题,andrew选择了一些有趣的模式去查询DOM,选择库就可以只修改一点点的代码。我并不绝对的认为这就是正确的或者最好的方法,但是我个人也在使用的一个手段。

 

Q:如果一个模块需要直接跟核心通信,可以么?

A:就像 Zakas 已经提到的,在技术上是没有理由说为什么模块不能跟核心直接联系的、但是这是一个很好的规则而已。如果你想要严格的坚持这个架构,你需要遵循这个规则或者选择一个松耦合的架构。

 

 

鸣谢

感谢Nicholas zakas他的工作带来的一系列想法。也感谢Andree Hansson提供了技术审核。

Rebecca Murphey, Justin Meyer, John Hann, Peter Michaux, Paul Irish and Alex Sexton,已经讨论过跟这个主题类似的话题,这是我和其他人灵感的源泉。

 

posted on 2013-04-30 11:07  李珠刚  阅读(652)  评论(0编辑  收藏  举报

导航