JavaScript模块化开发一瞥
对于那些正在构建大型应用程序,而对JavaScript不甚了解的开发者而言,他们最初必须要面对的挑战之一就是如何着手组织代码。起初只要在标记之间嵌入几百行代码就能跑起来,不过很快代码就会变得一塌糊涂……
对于那些正在构建大型应用程序,而对JavaScript不甚了解的开发者而言,他们最初必须要面对的挑战之一就是如何着手组织代码。起初只要在<script>
标记之间嵌入几百行代码就能跑起来,不过很快代码就会变得一塌糊涂。而问题是,JavaScript没有为组织代码提供任何明显帮助。从字面上看,C#有using
,Java有import
——而JavaScript一无所有。这迫使JavaScript编写者试验不同的约定,并使用现有的语言创建了一些切实可行的方法来组织大型JavaScript应用程序。
各种模式(patterns)、工具(tools)及惯例(practices)会形成现代JavaScript的基础,它们必将来自于语言本身实现之外。
模块模式(The Module Pattern)
用于解决组织代码问题、使用最为广泛的方法之一是模块模式(Module Pattern)。我尝试在下面解释一个基本示例,并讨论其若干特性。要想阅读更精彩的说明,并了解用尽各种不同方法的怪人,那么请参阅Ben Cherry的帖子——JavaScript Module Pattern: In-Depth(深入理解JavaScript模块模式)。
(function(lab49) {
function privateAdder(n1, n2) {
return n1 + n2;
}
lab49.add = function(n1, n2) {
return privateAdder(n1, n2);
};
})(window.lab49 = window.lab49 || {});
在上例中,我们使用了一些来自语言的基本功能,从而创造出在C#及Java等语言中见过的类似结构。
隔离(Isolation)
请注意,这段代码包在被立即调用的函数里(仔细看最后一行)。由于在浏览器中,默认情况下会把JavaScript文件置于全局作用域级别上进行计算(evaluated),因此在我们在文件中声明的任何内容都是随处可用的。想象一下,要是先在lib1.js中声明了var name = '...'
,然后又在lib2.js声明了var name = '...'
。那么后一句var声明就会替掉前一句的值——这可不太妙。然而,由于JavaScript拥有函数作用域级别,上例中所声明的一切都位于函数自身作用域内,与全局作用域毫无瓜葛。这意味着,无论系统将来如何变化,位于函数中的任何内容都不会受到影响。
命名空间(Namespacing)
在最后一行代码中会看到,我们要么将window.lab49
赋给其自身,要么将空对象{}
赋给它。尽管看起来有点儿怪,不过让我们一起来看下这样一个虚构系统,系统中的那些js文件一律使用了上例中的函数包装器(function wrapper)。
首个被引入的文件会计算那个或语句(...||...
),并发现左侧的表达式undefined(未定义)。由于undefined会被判定为假,因此或语句会进一步计算右侧表达式,在本例中就是空对象。或语句实际上是个表达式,它会返回计算结果,进而将结果赋给全局变量window.lab49
。
现在轮到接下来的文件使用此模式了,它会执行或语句,并发现window.lab49
目前已是对象实例——真(对象实例会被判定为真)。此时或语句会走捷径,并返回这个会立即赋给其自身的值——其实什么都没做。
由此导致的结果是,首个被引入的文件会创建lab49
命名空间(就是个JavaScript对象),而且所有使用这种结构的后续文件都只是重用此现有实例。
私有状态(Private State)
正如刚才所说,由于位于函数内部,在其内部声明的所有内容都处于该函数的作用域内,而非全局作用域。这对于隔离代码真是棒极了,不过它还带来了一种效果,那就是没人能调用它。真是中看不中用啊!
刚刚还谈到,创建window.lab49
对象是为了用命名空间来有效地管理我们的内容。而且由于变量lab49
被附加到window
对象上,因此它是全局可用的。为了把其中的内容公布给模块外部,或许有人会公开声称,我们要做的全部就是把一些值附加到那个全局变量上。正如上例中所写的add
函数一样。现在,在模块外部就可以通过lab49.add(2, 2)
来调用add
函数了。
在此函数中声明一些值的另一结果是,要是某个值没有通过将其附加到全局命名空间或者此模块外部的某个对象上的方式来显示公开,那么外部代码就访问不到该值。实际上,我们恰好创建了一些私有值。
CommonJS模块(CommonJS Modules)
CommonJS是个社团,主要由服务器端JavaScript运行库(server-side JavaScript runtimes)编写者组成,他们致力于将模块的公开及访问标准化的工作。值得注意的是,他们提议的模块系统并非标准,因为它不是出自制定JavaScript标准的同一社团,所以它更像是服务器端JavaScript运行库编写者彼此之间的非正式约定。
我通常会支持CommonJS的想法,但要搞清楚的是:它并不是一份崇高而神圣的规范(就像ES5一样);它只不过是一些人在邮件列表中所讨论的想法。而且多数想法都未付诸实现。
——Ryan Dahl, node.js的创造者
这份模块规范的核心相当直截了当。所有模块都要在其自身的上下文中进行计算,并且要有个全局变量exports
供模块使用。而全局变量exports
只是个普通的JavaScript对象,甚至可以自行往上面附加内容,它与上面展示的命名空间对象(lab49
)类似。要想访问某个模块,需调用全局函数require
,并指明所请求的包标识符。接着会计算此模块,而且无论返回何值都会将其附加到exports
上。然后会缓存此模块,以便后来的require
函数调用。
// calculator.js
// 计算器模块——译注
exports.add = function(n1, n2) {
};
// app.js
// 某个需要调用计算器模块的应用程序。
// './calculator'即包标识符。——译注
var calculator = require('./calculator');
calculator.add(2, 2);
要是摆弄过Node.js,或许会对以上代码有种似曾相识的感觉。这种用Node来实现CommonJS模块的方式真是出奇地简单,在node-inspector(一款Node调试器)中查看模块时,会显示其包装在函数内部的内容,这些内容正是传递给exports
及require
的值。非常类似于上面展示的手卷模块内容。
有几个node项目(Stitch及Browserify),它们将CommonJS模块带进了浏览器。服务器端组件会把这些彼此独立的模块js文件打包到单独的js文件中,并把那些代码用生成的模块包装器包起来。
CommonJS主要是为服务器端JavaScript运行库设计的,而且由于有几个属性使得它们难以在浏览器中组织客户端代码。
require
必须立即返回——要是已经拥有所有内容时这会工作得很好,不过这导致难以使用脚本加载器(script loader)去异步下载脚本。- 每个模块占一个文件——为了合并为CommonJS模块,必须把它们以某种风格组织起来,并包裹到一个函数中。要是没有类似于上面所提及的服务器组件,那么就难以使用它们,并且在许多环境(ASP.NET,Java)下这些服务器组件尚不存在。
异步模块定义(Asynchronous Module Definition)
异步模块定义(Asynchronous Module Definition,通常称为AMD)已被设计为适合于浏览器的模块格式。它起初源于CommonJS社团的提案,不过自从迁移到GitHub上以后,现已加入了配套的测试套件,以便模块系统编写者来验证其代码是否符合AMD的API。
AMD的核心是define
函数。调用define
函数最常见的方式是传入三个参数——模块名(也就是说不再与文件名绑定)、该模块依赖的模块标识符数组、以及将会返回该模块定义的工厂函数。(调用define
函数的其他方式——详细信息请参阅AMD wiki)。
// 定义calculator(计算器)模块。——译注
define('calculator', ['adder'], function(adder) {
// 返回具有add方法的匿名对象。——译注
return {
add: function(n1, n2) {
/*
* 实际调用的是adder(加法器)模块的add方法。
* 而且adder模块已在前一参数['adder']中指明了。——译注
*/
return adder.add(n1, n2);
}
};
});
由于此模块的定义包在define
函数的调用中,因此这意味着可以欣然将多个模块都放在单个js文件中。此外,由于当调用模块工厂函数define
时,模块加载器已拥有控制权,因此它可以自行安排时间去解决(模块间的)依赖关系——对于那些需要先异步下载的模块,真可谓得心应手。
为了与原先的CommonJS模块提案保持兼容已做出了巨大的努力。有些特殊行为是为了能在模块工厂函数中使用require
及exports
,这意味着,那些传统的CommonJS模块可直接拿来用。
看起来AMD正在成为颇受欢迎的组织客户端JavaScript应用程序的方式。无论是如RequireJS或curl.js等模块资源加载器,还是像Dojo等最近已支持AMD的JavaScript应用程序,情况都是如此。
这是否意味着JavaScript很烂?(Does this mean JavaScript sucks?)
缺乏语言级别的结构,而无法将代码组织到模块中,这可能会让来自其他语言的开发者觉得很不爽。然而,正因为此缺陷才迫使JavaScript开发者想出他们自己的模块组织模式,而且我们已经能够随着JavaScript应用程序的发展进行迭代并改进。欲深入了解此主题请访问Tagneto的博客。
想象一下,即便在10年前就已将此类功能引入语言。那么他们也不可能想到后来的那些需求,例如在服务器上运行大型JavaScript应用程序、在浏览器中异步加载资源、或者像text templates(文本模板)(就是些文本加载器,其功能类似于RequireJS)那样引入资源等等。
正在考虑将模块(Modules)作为Harmony/ECMAScript 6的语言级别功能。这多亏了模块系统编写者们的奇思妙想、以及过去数年中所做的辛勤工作,更有可能的是,我们最终将得到适合于构建现代JavaScript应用程序的语言。
查看英文原文:JavaScript Modules
http://www.ituring.com.cn/article/1091
JavaScript模块化开发一瞥的更多相关文章
- Javascript模块化开发,使用模块化脚本加载工具RequireJS,提高你代码的速度和质量。
随着前端JavaScript代码越来越重,如何组织JavaScript代码变得非常重要,好的组织方式,可以让别人和自己很好的理解代码,也便于维护和测试.模块化是一种非常好的代码组织方式,本文试着对Ja ...
- Javascript 模块化开发上线解决方案
最近又换部门了,好频繁地说...于是把这段时间搞的小工具们简单整理了一下,作了一个小的总结.这次用一个简单业务demo来向大家介绍一下Javascript模块化开发的方式和自动化合并压缩的一些自己的处 ...
- JavaScript模块化开发整理
在网上已经有很多关于模块化开发的文章了,这里还是按照自己的理解来整理一下. 随着项目文件的越来越大和需求的越来越贴近现实(我发现现在客户不如:一个领导说我要审批你们报上来的资料,系统发布以后用的还不错 ...
- Javascript模块化开发-轻巧自制
Javascript模块化开发-轻巧自制 一.前言现在javascript的流行,前端的代码越来越复杂,所以我们需要软件工程的思想来开发前端.模块化是必不可少的,这样不仅能够提高代码的可维护性.可扩展 ...
- JavaScript模块化开发的那些事
模块化开发在编程开发中是一个非常重要的概念,一个优秀的模块化项目的后期维护成本可以大大降低.本文主要介绍了JavaScript模块化开发的那些事,文中通过一个小故事比较直观地阐述了模块化开发的过程. ...
- 详解JavaScript模块化开发
什么是模块化开发? 前端开发中,起初只要在script标签中嵌入几十上百行代码就能实现一些基本的交互效果,后来js得到重视,应用也广泛起来了,jQuery,Ajax,Node.Js,MVC,MVVM等 ...
- 2.精通前端系列技术之JavaScript模块化开发 seajs(一)
在使用seajs模块化开发之前,直接在页面引用js会容易出现冲突及依赖相关的问题,具体问题如下 问题1:多人开发脚本的时候容易产生冲突(比如全局参数冲突,方法名冲突),可以使用命名空间降低冲突,不能完 ...
- JavaScript模块化开发实例
最近接触了一些JavaScript开发的例子,在这里与大家一起分享一下: 例子:当我们一个团队在写Js文件的时候,你一个人写的JS代码自己可以看懂也可以维护,但是别人想对你的JS进行扩展的话,如果都在 ...
- JavaScript模块化开发&&模块规范
在做项目的过程中通常会有一些可复用的通用性功能,之前的做法是把这个功能抽取出来独立为一个函数统一放到commonFunctions.js里面(捂脸),实现类似于snippets的代码片段收集. fun ...
随机推荐
- java --- 设计模式 --- 动态代理
Java设计模式——动态代理 java提供了动态代理的对象,本文主要探究它的实现, 动态代理是AOP(面向切面编程, Aspect Oriented Programming)的基础实现方式, 动态代理 ...
- IGF职业组比赛
IGF职业组比赛 参赛资格: 面向亚太区所有独立游戏开发者(参见详细规则) 截止日期: 2015年7月20日 2015年IGF职业组七大奖项设置如下: * 最佳游戏(RMB20, 000) * 最佳移 ...
- CSS3制作苹果风格键盘
aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAAtMAAAEICAIAAAASh+8XAAAgAElEQVR4nOzdaXBU14E3/FPVBVUq5X
- “iOS 推送通知”详解:从创建到设置到运行
这是一篇编译的文章,内容均出自Parse.com的iOS开发教程,同时作者还提供了视频讲解.本文将带领开发者一步一步向着iOS推送通知的深处探寻,掌握如何配置iOS推送通知的奥义. 介绍一点点背景资料 ...
- 【LeetCode】189 - Rotate Array
Rotate an array of n elements to the right by k steps. For example, with n = 7 and k = 3, the array ...
- This version of MySQL doesn’t yet support ‘LIMIT & IN/ALL/ANY/SOME 错误解决
在一个Mysql表达式中使用嵌套查询,出现了这个错误.原因是内层select语句带有limit子句. 在网上查了下,有文章指出: 比如这样的语句是不能正确执行的. select * from ta ...
- ShellExecute的各种用法
一.利用系统默认的邮件收发器发送电子邮件 Uses ..., ShellAPI; Var lpHwnd: HWND; lpOperation, lpFile, lpParameters, lpDire ...
- HDU ACM 3177 Crixalis's Equipment
Crixalis's Equipment Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Oth ...
- ActiveX控件
什么是ActiveX控件:一个进程内服务器,支持多种的COM接口.(可以理解为,一个COM接口是一个纯抽象基类,你实现了它,并且它支持自注册,就是一个ActiveX控件了)可以把ActiveX控件看做 ...
- 原生的AJAX写法,可以直接复制过来套用
方法一: function createXMLHTTPRequest() { //1.创建XMLHttpRequest对象 //这是XMLHttpReuquest对象无部使用中最复杂的一步 //需要针 ...