随着网站逐渐变成"互联网应用程序",嵌入网页的Javascript代码越来越庞大,越来越复杂。
网页越来越像桌面程序,需要一个团队分工协作、进度管理、单元测试等等......开发者不得不使用软件工程的方法,管理网页的业务逻辑。
Javascript模块化编程,已经成为一个迫切的需求。理想情况下,开发者只需要实现核心的业务逻辑,其他都可以加载别人已经写好的模块。
但是,Javascript不是一种模块化编程语言,它不支持"类"(class),更遑论"模块"(module)了。(正在制定中的ECMAScript标准第六版,将正式支持"类"和"模块",但还需要很长时间才能投入实用。)
Javascript社区做了很多努力,在现有的运行环境中,实现"模块"的效果。本文总结了当前"Javascript模块化编程"的最佳实践,说明如何投入实用。虽然这不是初级教程,但是只要稍稍了解Javascript的基本语法,就能看懂。
一、原始写法
模块就是实现特定功能的一组方法。
只要把不同的函数(以及记录状态的变量)简单地放在一起,就算是一个模块。
function m1(){
//...
}function m2(){
//...
}
上面的函数m1()和m2(),组成一个模块。使用的时候,直接调用就行了。
这种做法的缺点很明显:"污染"了全局变量,无法保证不与其他模块发生变量名冲突,而且模块成员之间看不出直接关系。
二、对象写法
为了解决上面的缺点,可以把模块写成一个对象,所有的模块成员都放到这个对象里面。
var module1 = new Object({
_count : 0,
m1 : function (){
//...
},m2 : function (){
//...
}});
上面的函数m1()和m2(),都封装在module1对象里。使用的时候,就是调用这个对象的属性。
module1.m1();
但是,这样的写法会暴露所有模块成员,内部状态可以被外部改写。比如,外部代码可以直接改变内部计数器的值。
module1._count = 5;
三、立即执行函数写法
使用"立即执行函数"(Immediately-Invoked Function Expression,IIFE),可以达到不暴露私有成员的目的。
var module1 = (function(){
var _count = 0;
var m1 = function(){
//...
};var m2 = function(){
//...
};return {
m1 : m1,
m2 : m2
};})();
使用上面的写法,外部代码无法读取内部的_count变量。
console.info(module1._count); //undefined
module1就是Javascript模块的基本写法。下面,再对这种写法进行加工。
四、放大模式
如果一个模块很大,必须分成几个部分,或者一个模块需要继承另一个模块,这时就有必要采用"放大模式"(augmentation)。
var module1 = (function (mod){
mod.m3 = function () {
//...
};return mod;
})(module1);
上面的代码为module1模块添加了一个新方法m3(),然后返回新的module1模块。
五、宽放大模式(Loose augmentation)
在浏览器环境中,模块的各个部分通常都是从网上获取的,有时无法知道哪个部分会先加载。如果采用上一节的写法,第一个执行的部分有可能加载一个不存在空对象,这时就要采用"宽放大模式"。
var module1 = ( function (mod){
//...
return mod;
})(window.module1 || {});
与"放大模式"相比,"宽放大模式"就是"立即执行函数"的参数可以是空对象。
六、输入全局变量
独立性是模块的重要特点,模块内部最好不与程序的其他部分直接交互。
为了在模块内部调用全局变量,必须显式地将其他变量输入模块。
var module1 = (function ($, YAHOO) {
//...
})(jQuery, YAHOO);
上面的module1模块需要使用jQuery库和YUI库,就把这两个库(其实是两个模块)当作参数输入module1。这样做除了保证模块的独立性,还使得模块之间的依赖关系变得明显。这方面更多的讨论,参见Ben Cherry的著名文章《JavaScript Module Pattern: In-Depth》。
这个系列的第二部分,将讨论如何在浏览器环境组织不同的模块、管理模块之间的依赖性。
(完)
greatghoul 说:
很简单的简单,很容易就看懂了,谢谢好文。
2012年10月26日 09:19 | # | 引用
CJ 说:
赶紧写下一篇。
JavaScript AMD : https://github.com/amdjs/amdjs-api/wiki/AMD
http://wiki.commonjs.org/wiki/Modules/AsynchronousDefinition
我的项目Package.js : https://code.google.com/p/package-js/
还有SeaJS : http://seajs.org/
2012年10月26日 10:44 | # | 引用
dulao5 说:
建议写写 Common JavaScript , 以及玉伯的 SeaJS : http://seajs.org/
2012年10月26日 13:10 | # | 引用
nakseuksa 说:
三,四,五,六的括号是不是错了?
2012年10月26日 14:14 | # | 引用
nakseuksa 说:
http://www.adequatelygood.com/2010/3/JavaScript-Module-Pattern-In-Depth
上面链接里的例子如下:
(function () {
// ... all vars and functions are in this scope only
// still maintains access to all globals
}());
和一峰给的代码的“()”的用法不一样。请问这两种写法有什么区别咧?
2012年10月26日 14:26 | # | 引用
nakseuksa 说:
http://nuysoft.iteye.com/blog/1177451
打开jQuery源码,首先你会看到这样的代码结构:
(function( window, undefined ) {
// jquery code
})(window);
1. 这是一个自调用匿名函数。什么东东呢?在第一个括号内,创建一个匿名函数;第二个括号,立即执行
2. 为什么要创建这样一个“自调用匿名函数”呢?
通过定义一个匿名函数,创建了一个“私有”的命名空间,该命名空间的变量和方法,不会破坏全局的命名空间。这点非常有用也是一个JS框架必须支持的功能,jQuery被应用在成千上万的JavaScript程序中,必须确保jQuery创建的变量不能和导入他的程序所使用的变量发生冲突。
3. 匿名函数从语法上叫函数直接量,JavaScript语法需要包围匿名函数的括号,事实上自调用匿名函数有两种写法:
(function() {
console.info( this );
console.info( arguments );
}( window ) );
------------------------------------
(function() {
console.info( this );
console.info( arguments );
})( window );
2012年10月26日 14:30 | # | 引用
liveM 说:
想了解一下 AMD module
2012年10月26日 22:10 | # | 引用
xiaotaostory 说:
大爱阮兄的js文章!果断分享之!
2012年10月27日 08:41 | # | 引用
王虎 说:
思路清晰,简单易懂。
2012年10月28日 11:24 | # | 引用
Z. 说:
那个,我觉得现在的javascript挺好的。也可以实现模块、类啊。就像汇编也可以做出来各种样式一样。
2012年10月28日 18:52 | # | 引用
ctx2002 说:
想问一问阮兄,javascript 适合编写长时间运行的程序吗?
我以前写过一个javascript的长时间运行的程序,但用户反应,
程序运行几个小时后,系统内存会不够。
我查了task manager,发现长时间在浏览器中运行javascript程序会导致
内存泄漏。不知阮兄遇到过这样的问题没有。
根据我上网搜查的结果显示,这可能是浏览器的问题。
我一直没有找到原因,不知道现在怎么样了。
还有,在你编写大型的javascript程序,会出现内存泄漏的情况吗?
2012年10月29日 08:55 | # | 引用
王金平 说:
简洁、清晰、透彻
2012年10月29日 09:34 | # | 引用
welpher.yu 说:
嗯,已经用了很长时间了
2012年10月29日 11:30 | # | 引用
陈三石 说:
你好我是一个菜鸟 为什么第三步 使用立即执行函数写法 可以保护私有变量不被改变
2012年10月31日 11:22 | # | 引用
jz 说:
在放大模式中,可以通过给传入的module进行扩展,从而实现“继承”,如果想扩展module的私有方法呢?
2012年10月31日 14:00 | # | 引用
artwl 说:
通俗易懂,不错
2012年10月31日 20:53 | # | 引用
踏雪无痕 说:
这个不错,收藏了
2012年11月 1日 21:48 | # | 引用
張旭 说:
如果使用 coffee-script 是不是就不用管這些了?
2012年11月 8日 15:15 | # | 引用
lds 说:
一样的
2012年11月 8日 19:18 | # | 引用
powerjiang 说:
这两种写法都可以,语法上都没有错误,国外好像都喜欢(function(){}());国内的人比较喜欢(function(){})();
2012年11月21日 10:54 | # | 引用
笋染夕林 说:
(function(){}())是使用了强制运算符执行函数调用运算,(function(){})()是通过函数调用运算符操作函数引用。两者功能上是一致的,只是运算过程不同。
2012年11月21日 22:03 | # | 引用
恩赐解脱 说:
好文,顶起!而且页面代码让人看起来特别舒服,越看越想往下看。。。爱不释手阿
2012年11月29日 19:37 | # | 引用
狂乱贵公子 说:
写的好帅~ 赞
2013年3月28日 14:10 | # | 引用
hbbbs 说:
这样的文章 真是 技术文章中的 精品啊!
干货 ,但入口即化!
2013年6月16日 15:40 | # | 引用
j60017268 说:
第四点没看明白,有人解释一下么?
2013年8月 5日 16:09 | # | 引用
warawara 说:
2013年9月 2日 08:01 | # | 引用
codezyc 说:
谢谢您的分享。
2013年9月24日 10:00 | # | 引用
程序猿小卡 说:
通俗易懂的好文!
2013年10月18日 18:08 | # | 引用
defcon 说:
非常感谢!写的文章通俗易懂,深入浅出啊!
2014年1月20日 09:54 | # | 引用
李朝 说:
第4部分:
var module1 = (function (mod){
mod.m3 = function () {
//...
};
return mod;
})(module1);
写成:
(function (mod){
mod.m3 = function () {
//...
};
})(module1);
或者:
module1.m3 = function () {
//...
};
同样都是给module1扩展了m3方法,请问有什么区别吗?
2014年1月22日 11:13 | # | 引用
王绍伟 说:
我怎么老觉得你的头像神似我在五道口面试时遇到的一个经理,但是上网搜了下你的资料说你是在上海教书的
2014年3月26日 18:10 | # | 引用
csu 说:
var module1 = (function(){
var _count = 0;
var m1 = function(){
//...
};
var m2 = function(){
//...
};
return {
m1 : m1,
m2 : m2
};
})();
var module1 = function(){
var _count = 0;
var m1 = function(){
//...
};
var m2 = function(){
//...
};
return {
m1 : m1,
m2 : m2
};
}();
请问这两种写法有什么区别嘛?第二种是我在《javascript高级程序设计》书中看到的。谢谢!
2014年4月16日 13:07 | # | 引用
paperman 说:
阮一峰兄文章通俗易懂,非常感谢你为我解答的很多问题!
2014年4月22日 11:34 | # | 引用
zhishaofei 说:
没区别
2014年6月 4日 16:31 | # | 引用
Hedgehog 说:
李朝和csu的问题谁来回答一下下,同问
2014年6月30日 15:32 | # | 引用
Hedgehog 说:
关于修改、读取私有变量,我记得可以这样子
var module1 = (function(){
var _count = 0;
var m1 = function(){
//...
};
var m2 = function(){
//...
};
var getCount = function(){
return this._count;
};
var setCount = function(newCount){
//当然,不需要修改的就不用构造这个方法
this._count = newCount;
};
return {
m1 : m1,
m2 : m2,
getCount : getCount,
setCount : setCount
};
})();
2014年6月30日 15:42 | # | 引用
Lessy 说:
非常好正好不懂这个方面,希望harmony快来,就不用这样hack了。
2014年7月10日 17:48 | # | 引用
lisg 说:
this._count; 应该改为_count;
2014年7月30日 14:18 | # | 引用
tab3o1 说:
李朝:
1、为了级联
比如 module1.m3().m2()
2、就是本文一直说的私有变量的问题了噻
这两种写法没区别
但是这么写就不对了:
(function(){
var _count = 0;
var m1 = function(){
//...
};
var m2 = function(){
//...
};
return {
m1 : m1,
m2 : m2
};
})();
function(){
var _count = 0;
var m1 = function(){
//...
};
var m2 = function(){
//...
};
return {
m1 : m1,
m2 : m2
};
}();
这里的第二种写法会解析语法树出错 。
2014年9月 5日 17:41 | # | 引用
morgan 说:
看到放大这个实现,眼前一亮啊!
2014年9月19日 10:35 | # | 引用
yuyuyu 说:
var module1 = (function (mod){
mod.m3 = function () {
//...
};
return mod;
})(module1);
这个不是报错吗???是不是写错了什么呢????
2014年9月24日 11:01 | # | 引用
tasann 说:
赞一个,好文章。
2014年10月29日 17:42 | # | 引用
艾瑞可 说:
感觉你的博客有点简陋,建议加个返回顶部
2014年12月11日 15:59 | # | 引用
stark 说:
老师好棒~学习了
2015年1月 7日 16:23 | # | 引用
Khan 说:
好文,简单易懂,学习了~~~~·
2015年2月 3日 10:19 | # | 引用
放开那条龙 说:
赞!来龙去脉写得很清楚。
原来模块本质上是“立即执行函数”返回的一个变量,另外跟 Python 类似,JavaScript 的作用域本质上也是字典。
2015年4月 8日 16:44 | # | 引用
EricYang 说:
正好解决我现在的问题;结合想着的项目;我学习学习!
2015年5月21日 18:35 | # | 引用
lordrobert 说:
12年就有了啊,只怪自己看到的太晚!
2015年5月28日 09:17 | # | 引用
HOYO 说:
四的代码有点问题。
var module1 = (function (mod){
mod.m3 = function () {
//...
};
return mod;
})(module1);
你的module1在定义的时候进行了引用。除非之前定义过module1为一个对象,但是以前定义过的话这里又重复定义了。
2015年5月31日 09:17 | # | 引用
福建恰好 说:
一直关注大牛的文章,只能说 真是深入浅出 很好的讲解 对学习者是个很好的带入。
2015年9月 6日 10:21 | # | 引用
nxy 说:
好文,通俗易懂。赞一个!
2015年9月16日 16:01 | # | 引用
MwumLi 说:
最后一个什么意思,没太懂
2015年10月29日 18:56 | # | 引用
marsman 说:
简单明了,比我买的那本《Javascript设计模式》好多了,那本书是翻译的国外的,狗屁不通。。。。
2015年12月21日 17:15 | # | 引用
飞鸿影 说:
var module1 = (function (mod){
mod.m3 = function () {
//...
};
return mod;
})(module1);
已知模块module1,在匿名函数内别名mod。追加m3对象后,返回mod。这时候module1也就有了m3对象。
2016年1月 9日 00:03 | # | 引用
飞鸿影 说:
但是module1可能不存在,这样会报错。我们再改善改善。
var module1 = ( function (mod){
//...
return mod;
})(window.module1 || {});
这时候如果不存在全局的module1,传入{}空对象即可。切记不可用module1替换window.module1。
2016年1月 9日 00:04 | # | 引用
Hugh 说:
2016年3月24日 18:14 | # | 引用
视识祈 说:
阮老师讲得挺好,挺细致,再加上诸多评论者的补充,效果真好。谢谢大家了。
2016年3月25日 11:12 | # | 引用
qingqiu 说:
能详细说一下为什么一定要用window.module1么?直接用module1不行么?
2016年3月31日 13:37 | # | 引用
liuyufang 说:
我也很想问为什么不能用module1呢?而必须用window.module1?
后来我测试了一下,其实都是可以的代码如下:
var module1 = (function(mod){
mod.said = function(){
alert("hello liuyufang");
}
return mod;
})(module1||{});
module1.said();
2016年4月 4日 18:46 | # | 引用
liuyufang 说:
不知道老师您是否还在看这里?在第二种写法里真的能运行吗?因为在我的认知里,匿名函数的自执行只有两种写法,一种是(function(){}()),另一种是(function(){})(),而第二种的写法上不应该是到达最后一个}后就会认为结束吗?
2016年4月 4日 19:00 | # | 引用
李大侠 说:
楼主说的第三点,立即执行函数无法访问私有变量是错的,无法访问的实质是这个变量不属于这个对象,for example:
var createObj=function(){
var count=3;
this.username="lidaxia";
this.getUserName=function(){
return this.username;
};
return this;
};
var person=createObj();
alert(person.count);
是无法访问count的,但是能访问username,你那个立即执行函数其实没有将变量赋值给对象而已,就像我这个
2016年4月 6日 18:18 | # | 引用
inegi 说:
刚试了下,(function(){}())和(function(){})()能执行,var x = function(){}()也能执行,但是function(){}();这个就报错了,函数没名字。而function xx(){}();这个有名字的不报错,但是不执行到达最后一个}后就会认为结束
2016年5月 6日 10:08 | # | 引用
fulus 说:
第二种方法前面加了变量确是可用,如果只写function(){...}();会报错,前面加了变量是否被认为就不是匿名变量???
2016年5月19日 10:39 | # | 引用
frank 说:
var module1 = (function(){
var _count = 0;
var m1 = function(){
//...
};
var m2 = function(){
//...
};
return {
m1 : m1,
m2 : m2
};
})();
函数表达式直接在最后面加小括号不是可以直接立即调用吗,为什么还要用小括号括起来
2016年7月13日 17:55 | # | 引用
frank 说:
放大模式是如何实现放大和继承的呢?
2016年7月13日 18:48 | # | 引用
Lity 说:
@陈三石 我也是菜鸟,我认为是这样的,函数结束return,返回值代替了函数,所以外部是读不到除了返回值以外的东西的。
2016年8月25日 11:13 | # | 引用
Leon 说:
我认为楼主在 立即执行函数写法 写错了,不是对象怎么可以通过键值的形式去得到变量呢?就算没有return也是undefined
2016年8月25日 11:25 | # | 引用
TJ 说:
@李大侠:
你这样写 person不就是等于当前上下文的全局对象window||global,username和getUserName 不就变成全局对象的属性
2016年9月 1日 09:06 | # | 引用
xgqfrms 说:
闭包:
Closures are functions that refer to independent (free) variables (variables that are used locally, but defined in an enclosing scope). In other words, these functions 'remember' the environment in which they were created.
2016年9月 5日 12:59 | # | 引用
xgqfrms 说:
AMD, CMD, CommonJS和UMD
AMD模块以浏览器第一的原则发展,异步加载模块;
CommonJS模块以服务器第一原则发展,选择同步加载;
UMD是AMD和CommonJS的糅合;
2016年9月 5日 20:00 | # | 引用
只爱叶子的夏天 说:
看完,对JS模块的理解算是有了一个入门。谢谢
2016年10月11日 00:47 | # | 引用
Felix 说:
可是如果用户调用了module1.setCount()怎么办,_count这个变量岂不是依然很不安全?
2016年10月15日 01:10 | # | 引用
小朔 说:
第六种全局模式的没有太明白。
假设页面中引用了jquery,不需要在立即执行函数的参数传递中传入jquery,内部也是可以调用$的写法呀。
2016年11月17日 13:23 | # | 引用
MinYong 说:
难道你们没发现 立即执行函数有问题? m1的()都没加.
//立即执行函数
var module = (function(){
var _count = 0;
var m1 = function() {
console.log("m1");
};
var m2 = function() {
console.log("m2");
};
return {
m1 : m1(),
m2 : m2()
};
})();
2016年12月16日 11:09 | # | 引用
1234 说:
不加()才是对的,因为是将函数复制给了m1变量,并不是让他运行,而是在实例化的时候采用 module.m1()运行
2016年12月20日 10:16 | # | 引用
sx 说:
当老师的人讲课就是很好,由浅入深由表及里的讲解,感谢博主的贡献。
2017年2月 9日 15:53 | # | 引用
Cherry 说:
我也遇到了同样的问题,这里给你说一下出现报错的原因,因为“放大模式”可以认为是扩充原有的函数,达到“放大的目的”,所以在引用这一段代码前是需要先有一个module1函数的,你看下你是不是直接就执行了这段代码
另求follow,求star,我的github个人主页:sunshine940326.github.io
2017年3月 8日 15:02 | # | 引用
张成龙 说:
讲的是很好,但是对于初学者来说还是很难运用到自己的项目中啊!比如我现在的项目就是一个页面上的直播,在这个页面上的功能非常多,比如自定义的进度条,窗口根据位置不同和用户的操作变化大小,上拉刷新,发送评论等等,我的写法就是直接把一个又一个的功能一个接一个的写在一个js文件中,然后引入,但是里面全局变量非常多,以后还要加更多的功能,所以我就想的用个什么办法能把我的这些功能管理一下,这篇文章讲的很好,但是我根本不知道怎么运用啊!
2017年3月14日 09:52 | # | 引用
小年 说:
2017年3月29日 19:57 | # | 引用
ykyk 说:
这个跟变量的作用域有关,它们是局部变量,所以不能在外部获取到,既然都获取不到了,何谈修改,也就是保护了私有变量不被修改。
2017年4月12日 09:33 | # | 引用
king 说:
var module = (function(){
var m1 = function(){};
var m2 = function(){};
return{m1:m1,m2:m2}
})(windows.module||{});
我想在外部 写new module();
那代码里面要怎么写构造函数呢
2017年4月25日 14:54 | # | 引用
上官 说:
做过的项目 用到的东西 去年开始接触模块化编程 到现在都是一头雾水 网上太多的理论讲得通篇都是书面 越看越不理解 今天看到大佬写的概论 结合之前项目用到的 一目了然 豁然开朗 谢谢大佬解惑。
2017年6月20日 17:17 | # | 引用
欧阳 说:
阮神五年前的文章,而五年前我却在打DOTA,现在真是兀自忧伤
2017年7月 9日 21:51 | # | 引用
古塘春草梦 说:
写得真得比其他网站的好太多,来龙去脉,一清二楚,感谢分享!
2017年7月30日 12:09 | # | 引用
bobLiao 说:
好话都只是简单地说一遍。看过阮大哥的博客,感觉是 简单而又不简单,估计就是阅文无数之后的干活,一语中的,还通古今。
2017年8月21日 11:29 | # | 引用
lu在脚下 说:
阮老师的这篇博客真好,虽然看起来非常简单,但是内在还有很多精华,细细品味可以学到很多东西。我做下我的理解总结
1.闭包的常用5种写法
(function(){}())
(function(){})()
这两种写法保证了不直接以function开头,所以下面的写法也是一样的
var a = function(){}()
var a = (function(){}())
var a = (function(){})() //习惯用这种
其他情况不讨论了
2.有同学在问第三的放大模式和下面的有什么区别,
var module1 = (function (mod){
mod.m3 = function () {
//...
};
return mod;
})(module1);
mod.m3 = function () {};
其实没区别,老师的这种写法保证了如果你同时需要定义私有变量的时候就用老师的方法,下面的那种方法不用老师特别指出大家都懂。
3.有同学吐槽例子4放大模式问题,我这里解释下
老师都说了,如果需要继承的话,可以使用放大模式,那么module1已经存在了,如果你不确定的话module已经存在的话可以使用第5种宽放大模式,老师分析的情况是一步步深入的,
4.有的同学在问为什么一定要使用window.module1,我咋听起来觉得奇怪,然后测试了下,没问题,接着就开始猜想出现什么情况会导致一定要使用window.module1,那么假设出现这种情况
var module1 = (function(){
count = 1
var m1 = function(){
}
return { m1:m1}
})()
(function(){
var module1 = ( function (mod){
//...
return mod;
})(module1 || {});
})()
那么这种情况错误了,因为在下面的这个闭包里面 module1 被初始化为undefined,此时(module1 || {} 的运算结果是{}),所以第一个module的作用就没了,因此要使用window.module1保证module1是全局变量
(function(){
var module1 = ( function (mod){
//...
return mod;
})(window.module1 || {});
})()
如果我的发言出错了希望大家给我指出下
2017年9月 4日 14:05 | # | 引用
Andy 说:
使用闭包避免全局变量污染,可以减少内容泄露
2017年12月26日 16:13 | # | 引用
hello, World 说:
我现在有点不清楚,什么时候用类模式, 什么时候用模块化的模式?
2018年5月 7日 14:19 | # | 引用
anikzm 说:
深入浅出,我这样的小白也能慢慢看懂,感谢分享!
2021年1月13日 16:31 | # | 引用