分类

Javascript模块化编程(一):模块的写法

作者: 阮一峰

日期: 2012年10月26日

随着网站逐渐变成"互联网应用程序",嵌入网页的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》

这个系列的第二部分,将讨论如何在浏览器环境组织不同的模块、管理模块之间的依赖性。

(完)

珠峰培训

简寻

留言(63条)

很简单的简单,很容易就看懂了,谢谢好文。

建议写写 Common JavaScript , 以及玉伯的 SeaJS : http://seajs.org/

三,四,五,六的括号是不是错了?

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
}());

和一峰给的代码的“()”的用法不一样。请问这两种写法有什么区别咧?

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 );

想了解一下 AMD module

大爱阮兄的js文章!果断分享之!

思路清晰,简单易懂。

那个,我觉得现在的javascript挺好的。也可以实现模块、类啊。就像汇编也可以做出来各种样式一样。

想问一问阮兄,javascript 适合编写长时间运行的程序吗?
我以前写过一个javascript的长时间运行的程序,但用户反应,
程序运行几个小时后,系统内存会不够。

我查了task manager,发现长时间在浏览器中运行javascript程序会导致
内存泄漏。不知阮兄遇到过这样的问题没有。
根据我上网搜查的结果显示,这可能是浏览器的问题。
我一直没有找到原因,不知道现在怎么样了。

还有,在你编写大型的javascript程序,会出现内存泄漏的情况吗?

简洁、清晰、透彻

嗯,已经用了很长时间了

你好我是一个菜鸟 为什么第三步 使用立即执行函数写法 可以保护私有变量不被改变

在放大模式中,可以通过给传入的module进行扩展,从而实现“继承”,如果想扩展module的私有方法呢?

通俗易懂,不错

这个不错,收藏了

如果使用 coffee-script 是不是就不用管這些了?

引用張旭的发言:

如果使用 coffee-script 是不是就不用管這些了?

一样的

引用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
}());

和一峰给的代码的“()”的用法不一样。请问这两种写法有什么区别咧?

这两种写法都可以,语法上都没有错误,国外好像都喜欢(function(){}());国内的人比较喜欢(function(){})();

引用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
}());

和一峰给的代码的“()”的用法不一样。请问这两种写法有什么区别咧?


(function(){}())是使用了强制运算符执行函数调用运算,(function(){})()是通过函数调用运算符操作函数引用。两者功能上是一致的,只是运算过程不同。

好文,顶起!而且页面代码让人看起来特别舒服,越看越想往下看。。。爱不释手阿

写的好帅~ 赞

这样的文章 真是 技术文章中的 精品啊!

干货 ,但入口即化!

第四点没看明白,有人解释一下么?

引用陈三石的发言:

你好我是一个菜鸟 为什么第三步 使用立即执行函数写法 可以保护私有变量不被改变

引文执行函数只是返回了函数m1,m2

谢谢您的分享。

通俗易懂的好文!

非常感谢!写的文章通俗易懂,深入浅出啊!

第4部分:
var module1 = (function (mod){
    mod.m3 = function () {
      //...
    };
    return mod;
  })(module1);
写成:
(function (mod){
    mod.m3 = function () {
      //...
    };
  })(module1);
或者:
module1.m3 = function () {
      //...
    };
同样都是给module1扩展了m3方法,请问有什么区别吗?

我怎么老觉得你的头像神似我在五道口面试时遇到的一个经理,但是上网搜了下你的资料说你是在上海教书的

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高级程序设计》书中看到的。谢谢!

阮一峰兄文章通俗易懂,非常感谢你为我解答的很多问题!

引用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
}());

和一峰给的代码的“()”的用法不一样。请问这两种写法有什么区别咧?


没区别

李朝和csu的问题谁来回答一下下,同问

关于修改、读取私有变量,我记得可以这样子
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
    };
  })();

非常好正好不懂这个方面,希望harmony快来,就不用这样hack了。

引用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
    };
  })();

this._count; 应该改为_count;


李朝:

1、为了级联
比如 module1.m3().m2()
2、就是本文一直说的私有变量的问题了噻

引用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高级程序设计》书中看到的。谢谢!

这两种写法没区别
但是这么写就不对了:
(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
    };
  }();
这里的第二种写法会解析语法树出错 。

看到放大这个实现,眼前一亮啊!

var module1 = (function (mod){
    mod.m3 = function () {
      //...
    };
    return mod;
  })(module1);

这个不是报错吗???是不是写错了什么呢????

赞一个,好文章。

感觉你的博客有点简陋,建议加个返回顶部

老师好棒~学习了

好文,简单易懂,学习了~~~~·

赞!来龙去脉写得很清楚。

原来模块本质上是“立即执行函数”返回的一个变量,另外跟 Python 类似,JavaScript 的作用域本质上也是字典。

正好解决我现在的问题;结合想着的项目;我学习学习!

12年就有了啊,只怪自己看到的太晚!

四的代码有点问题。
var module1 = (function (mod){
    mod.m3 = function () {
      //...
    };
    return mod;
  })(module1);
你的module1在定义的时候进行了引用。除非之前定义过module1为一个对象,但是以前定义过的话这里又重复定义了。

一直关注大牛的文章,只能说 真是深入浅出 很好的讲解 对学习者是个很好的带入。

好文,通俗易懂。赞一个!

最后一个什么意思,没太懂

简单明了,比我买的那本《Javascript设计模式》好多了,那本书是翻译的国外的,狗屁不通。。。。

var module1 = (function (mod){
    mod.m3 = function () {
      //...
    };
    return mod;
  })(module1);
已知模块module1,在匿名函数内别名mod。追加m3对象后,返回mod。这时候module1也就有了m3对象。

但是module1可能不存在,这样会报错。我们再改善改善。
var module1 = ( function (mod){
    //...
    return mod;
  })(window.module1 || {});

这时候如果不存在全局的module1,传入{}空对象即可。切记不可用module1替换window.module1。

引用yuyuyu的发言:

var module1 = (function (mod){
    mod.m3 = function () {
      //...
    };
    return mod;
  })(module1);

这个不是报错吗???是不是写错了什么呢????

整体是变量声明与赋值表达式的结合,按照优先级会先声明module1变量,然后计算“=”右侧表达式的值,最后将计算到的值赋给声明的变量module1。但在计算右侧表达式时,匿名函数会立即执行,立即执行时会传入优先声明的变量module1,但是这时候module1只是一个没有类型的空变量,作为实参引入匿名函数中后,其并不能设置属性,因为匿名函数中的形参mod不是一个对象,这时就会报错。 这样修改一下就行: var module1 = {} module1 = (function (mod){ mod.m3 = function () { //... }; return mod; })(module1);

阮老师讲得挺好,挺细致,再加上诸多评论者的补充,效果真好。谢谢大家了。

引用飞鸿影的发言:

但是module1可能不存在,这样会报错。我们再改善改善。
var module1 = ( function (mod){
    //...
    return mod;
  })(window.module1 || {});

这时候如果不存在全局的module1,传入{}空对象即可。切记不可用module1替换window.module1。


能详细说一下为什么一定要用window.module1么?直接用module1不行么?

我也很想问为什么不能用module1呢?而必须用window.module1?
后来我测试了一下,其实都是可以的代码如下:
var module1 = (function(mod){
mod.said = function(){
alert("hello liuyufang");
}
return mod;
})(module1||{});

module1.said();

引用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高级程序设计》书中看到的。谢谢!

不知道老师您是否还在看这里?在第二种写法里真的能运行吗?因为在我的认知里,匿名函数的自执行只有两种写法,一种是(function(){}()),另一种是(function(){})(),而第二种的写法上不应该是到达最后一个}后就会认为结束吗?

楼主说的第三点,立即执行函数无法访问私有变量是错的,无法访问的实质是这个变量不属于这个对象,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,你那个立即执行函数其实没有将变量赋值给对象而已,就像我这个

引用liuyufang的发言:

不知道老师您是否还在看这里?在第二种写法里真的能运行吗?因为在我的认知里,匿名函数的自执行只有两种写法,一种是(function(){}()),另一种是(function(){})(),而第二种的写法上不应该是到达最后一个}后就会认为结束吗?

刚试了下,(function(){}())和(function(){})()能执行,var x = function(){}()也能执行,但是function(){}();这个就报错了,函数没名字。而function xx(){}();这个有名字的不报错,但是不执行到达最后一个}后就会认为结束

引用liuyufang的发言:

不知道老师您是否还在看这里?在第二种写法里真的能运行吗?因为在我的认知里,匿名函数的自执行只有两种写法,一种是(function(){}()),另一种是(function(){})(),而第二种的写法上不应该是到达最后一个}后就会认为结束吗?

第二种方法前面加了变量确是可用,如果只写function(){...}();会报错,前面加了变量是否被认为就不是匿名变量???

我要发表看法

«-必填

«-必填,不公开

«-我信任你,不会填写广告链接