了解模块化开发heero发表于2013-6-1210:51分类:前端开发浏览次数:502评论次数:2小A是某个创业团队的前端工程师,负责编写项目的Javascript程序。全局变量冲突根据自己的经验,小A先把一些常用的功能抽出来,写成函数放到一个公用文件base.js中:var_={$:function(id){returndocument.getElementById(id);},getCookie:function(key){...},setCookie:function(key,value){...}};小A把这些函数都放在_对象内,以防过多的全局变量造成冲突。他告诉团队的其他成员,如果谁想使用这些函数,只要引入base.js就可以了。小C是小A的同事,他向小A反映:自己的页面引入了一个叫做underscore.js的类库,而且,这个类库也会占用_这个全局变量,这样一来就会跟base.js中的_冲突了。小A心想,underscore.js是第三方类库,估计不好改,但是base.js已经在很多页面铺开,不可能改。最后小A只好无奈地把underscore.js占用的全局变量改了。此时,小A发现,把函数都放在一个名字空间内,可以减少全局变量冲突的概率,却没有解决全局变量冲突这个问题。依赖随着业务的发展,小A又编写了一系列的函数库和UI组件,比方说标签切换组件tabs.js,此组件需调用base.js以及util.js中的函数。有一天,新同事小D跟小A反映,自己已经在页面中引用了tabs.js,功能却不正常。小A一看就发现问题了,原来小D不知道tabs.js依赖于base.js以及util.js,他并没有添加这两个文件的引用。于是,他马上进行修改:scriptsrc=tabs.js/scriptscriptsrc=base.js/scriptscriptsrc=util.js/script然而,功能还是不正常,此时小A教训小D说:“都说是依赖,那被依赖方肯定要放在依赖方之前啊”。原来小D把base.js和util.js放到tabs.js之后了。小A心想,他是作者,自然知道组件的依赖情况,但是别人就难说了,特别是新人。过了一段时间,小A给标签切换组件增加了功能,为了实现这个功能,tabs.js还需要调用ui.js中的函数。这时,小A发现了一个严重的问题,他需要在所有调用了tabs.js的页面上增加ui.js的引用!!!又过了一段时间,小A优化了tabs.js,这个组件已经不再依赖于util.js,所以他在所有用到tabs.js的页面中移除了util.js的引用,以提高性能。他这一修改,出大事了,测试组MM告诉他,有些页面不正常了。小A一看,恍然大悟,原来某些页面的其他功能用到了util.js中的函数,他把这个文件的引用去掉导致出错了。为了保证功能正常,他又把代码恢复了。小A又想,有没有办法在修改依赖的同时不用逐一修改页面,也不影响其他功能呢?模块化小A逛互联网的时候,无意中发现了一种新奇的模块化编码方式,可以把它之前遇到的问题全部解决。在模块化编程方式下,每个文件都是一个模块。每个模块都由一个名为define的函数创建。例如,把base.js改造成一个模块后,代码会变成这样:define(function(require,exports,module){exports.$=function(id){returndocument.getElementById(id);};exports.getCookie=function(key){...};exports.setCookie=function(key,value){...};});base.js向外提供的接口都被添加到exports这个对象。而exports是一个局部变量,整个模块的代码都没有占用半个全局变量。那如何调用某个模块提供的接口呢?以tabs.js为例,它要依赖于base.js和util.js:define(function(require,exports,module){var_=require('base.js'),util=require('util.js');vardiv_tabs=_.$('tabs');//....其他代码});一个模块可以通过局部函数require获取其他模块的接口。此时,变量_和util都是局部变量,并且,变量名完全是受开发者控制的,如果你不喜欢_,那也可以用base:define(function(require,exports,module){varbase=require('base.js'),util=require('util.js');vardiv_tabs=base.$('tabs');//....其他代码});一旦要移除util.js、添加ui.js,那只要修改tabs.js就可以了:define(function(require,exports,module){varbase=require('base.js'),ui=require('ui.js');vardiv_tabs=base.$('tabs');//....其他代码});加载器由于缺乏浏览器的原生支持,如果我们要用模块化的方式编码,就必须借助于一个叫做加载器(loader)的东西。目前加载器的实现有很多,比如require.js、seajs。而jRaiser类库也有自己的加载器。研究JavaScript模块化开发【精华汇总】浏览63次收藏0次转发1次推荐0次了解模块化开发图灵上有一篇DavidPadbury写的《JavaScript模块化开发一瞥》正在考虑将模块(Modules)作为Harmony/ECMAScript6的语言级别功能。这多亏了模块系统编写者们的奇思妙想、以及过去数年中所做的辛勤工作,更有可能的是,我们最终将得到适合于构建现代JavaScript应用程序的语言。对于那些正在构建大型应用程序,而对JavaScript不甚了解的开发者而言,他们最初必须要面对的挑战之一就是如何着手组织代码。起初只要在标记之间嵌入几百行代码就能跑起来,不过很快代码就会变得一塌糊涂……对于那些正在构建大型应用程序,而对JavaScript不甚了解的开发者而言,他们最初必须要面对的挑战之一就是如何着手组织代码。起初只要在script标记之间嵌入几百行代码就能跑起来,不过很快代码就会变得一塌糊涂。而问题是,JavaScript没有为组织代码提供任何明显帮助。从字面上看,C#有using,Java有import——而JavaScript一无所有。这迫使JavaScript编写者试验不同的约定,并使用现有的语言创建了一些切实可行的方法来组织大型JavaScript应用程序。各种模式(patterns)、工具(tools)及惯例(practices)会形成现代JavaScript的基础,它们必将来自于语言本身实现之外。——RebeccaMurphy模块模式(TheModulePattern)用于解决组织代码问题、使用最为广泛的方法之一是模块模式(ModulePattern)。我尝试在下面解释一个基本示例,并讨论其若干特性。要想阅读更精彩的说明,并了解用尽各种不同方法的怪人,那么请参阅BenCherry的帖子——JavaScriptModulePattern:In-Depth(深入理解JavaScript模块模式)。1234567891011(function(lab49){functionprivateAdder(n1,n2){returnn1+n2;}lab49.add=function(n1,n2){returnprivateAdder(n1,n2);};})(window.lab49=window.lab49||{});在上例中,我们使用了一些来自语言的基本功能,从而创造出在C#及Java等语言中见过的类似结构。隔离(Isolation)请注意,这段代码包在被立即调用的函数里(仔细看最后一行)。由于在浏览器中,默认情况下会把JavaScript文件置于全局作用域级别上进行计算(evaluated),因此在我们在文件中声明的任何内容都是随处可用的。想象一下,要是先在lib1.js中声明了varname='...',然后又在lib2.js声明了varname='...'。那么后一句var声明就会替掉前一句的值——这可不太妙。然而,由于JavaScript拥有函数作用域级别,上例中所声明的一切都位于函数自身作用域内,与全局作用域毫无瓜葛。这意味着,无论系统将来如何变化,位于函数中的任何内容都不会受到影响。命名空间(Namespacing)在最后一行代码中会看到,我们要么将window.lab49赋给其自身,要么将空对象{}赋给它。尽管看起来有点儿怪,不过让我们一起来看下这样一个虚构系统,系统中的那些js文件一律使用了上例中的函数包装器(functionwrapper)。首个被引入的文件会计算那个或语句(...||...),并发现左侧的表达式undefined(未定义)。由于undefined会被判定为假,因此或语句会进一步计算右侧表达式,在本例中就是空对象。或语句实际上是个表达式,它会返回计算结果,进而将结果赋给全局变量window.lab49。现在轮到接下来的文件使用此模式了,它会执行或语句,并发现window.lab49目前已是对象实例——真(对象实例会被判定为真)。此时或语句会走捷径,并返回这个会立即赋给其自身的值——其实什么都没做。由此导致的结果是,首个被引入的文件会创建lab49命名空间(就是个JavaScript对象),而且所有使用这种结构的后续文件都只是重用此现有实例。私有状态(PrivateState)正如刚才所说,由于位于函数内部,在其内部声明的所有内容都处于该函数的作用域内,而非全局作用域。这对于隔离代码真是棒极了,不过它还带来了一种效果,那就是没人能调用它。真是中看不中用啊!刚刚还谈到,创建window.lab49对象是为了用命名空间来有效地管理我们的内容。而且由于变量lab49被附加到window对象上,因此它是全局可用的。为了把其中的内容公布给模块外部,或许有人会公开声称,我们要做的全部就是把一些值附加到那个全局变量上。正如上例中所写的add函数一样。现在,在模块外部就可以通过lab49.add(2,2)来调用add函数了。在此函数中声明一些值的另一结果是,要是某个值没有通过将其附加到全局命名空间或者此模块外部的某个对象上的方式来显示公开,那么外部代码就访问不到该值。实际上,我们恰好创建了一些私有值。CommonJS模块(CommonJSModules)CommonJS是个社团,主要由服务器端JavaScript运行库(server-sideJavaScriptruntimes)编写者组成,他们致力于将模块的公开及访问标准化的工作。值得注意的是,他们提议的模块系统并非标准,因为它不是出自制定JavaScript标准的同一社团,所以它更像是服务器端JavaScript运行库编写者彼此之间的非正式约定。我通常会支持CommonJS的想法,但要搞清楚的是:它并不是一份崇高而神圣的规范(就像ES5一样);它只不过是一些人在邮件列表中所讨论的想法。而且多数想法都未付诸实现。——RyanDahl,node.js的创造者这份模块规范的核心相当直截了当。所有模块都要在其自身的上下文中进行计算,并且要有个全局变量exports供模块使用。而全局变量exports只是个普通的JavaScript对象,甚至可以自行往上面附加内容,它与上面展示的命名空间对象(lab49)类似。要想访问某个模块,需调用全局函数require,并指明所请求的包标识符。接着会计算此模块,而且无论返回何值都会将其附加到exports上。然后会缓存此模块,以便后来的require函数调用。123456789101112//calculator