JS模块化编程总结_待续
发布于 9 年前 作者 xiaolulu 6066 次浏览 最后一次编辑是 8 年前 来自 分享

###一、背景

随着的互联网技术的不断发展,浏览器逐渐成了集大成的CS客户端,页面功能在向系统级、软件级靠拢的趋势下,开发团队也需要一些软件工程的方法来开发WEB项目,如测试驱动、面向对象,而模块化编程更是成为一种急需应用的技术。

###二、原因

为方便文件管理、增加复用,我们需要细化JS文件,每个文件负责单一职能,称之为模块,明确每个文件的职能,当交互功能较复杂时,引用的文件也越加庞大,此时我们就需要模块化管理。

如我们编写一个模块组件Dialog.js,其依赖event.js / bootstrap.js / component.js,而event.js又依赖 base.js / core.js / el.js,bootstrap.js依赖 boot.js / under.js / almond.js,同理可能依赖更多的js,传统方法我们用到Dialog.js时实际上需要引用9个js,如下

<html>
<body>
...
 <script src="/public/base.js"></script>
 <script src="/public/core.js"></script>
 <script src="/public/el.js"></script>
 <script src="/public/boot.js"></script>
 <script src="/public/under.js"></script>
 <script src="/public/almond.js"></script>
 <script src="/module/event.js"></script>
 <script src="/module/bootstrap.js"></script>
 <script src="/module/component.js"></script>
 <script src="/widget/dialog.js"></script>
</body>
</html>

我们将其细分,Dialog所依赖的每个js都负责了某一独立功能,分开便于其它组件复用,也利于学习理解。

但将这9个js都到html里显示引用,工作量却非常可观,且不能弄错路径及加载顺序,若后续Dialog.js又需要多引用一个js,所有的项目都要手动增加,且这样依次同步加载下来会导致会占用浏览器线程,可能导致假死。

###三、作用

模块化的作用就是解决上面提到的两点问题。

1、实现js文件的异步加载,避免浏览器假死。 2、管理模块之间的依赖,便于模块的维护

###四、实现

模块化,就是把一个相对独立的功能,单独成一个文件,可输入指定依赖、输出指定的函数,供外界调用,其它都是内部的。这样即可方便不同的项目重复使用,也不会对其造成额外的影响。而既然要给不同的项目应用,特别像git上的模块是面向全球范围的,必然要形成可遵循的规范,开发者和使用者都有据可寻。

目前通用javascript规范分为服务端(CommonJS),客户端(AMD / CMD)。

CommonJS是针对javascript的服务端,一切模块都是通过require来同步加载调用, Nodejs采用的就是这个规范。

var module1 = require( 'module1'),
      module2 = require( 'module2' );

function module3(){
     ...
 }
exports.module3 = module3;

注:通过require加载module1、2,操作后通过exports导致module3。

这里我们主要讲客户端模块

客户端与服务端的不同在于异步加载,之所以异步是因为CommonJS规则应用在服务端时,文件都是本地硬盘读取,不受限于网络,等待硬盘读取时间很短。而浏览器要考虑网速,若长时间等待会导致浏览器假死。

引用形式如下图

amd pic

###五、AMD

是"Asynchronous Module Definition"的缩写,意思就是"异步模块定义",

以require.js为代表,官网http://requirejs.org/ ,其形式主要是

=============main.js===================
 <script src="js/require.js" data-main="js/Dialog"></script>

==============Dialog.js=======================
 define( ['event', 'bootstrap', 'component' ], function( event, bootstrap, component ){
      event.doSomthing();
      bootstrap.doSomthing();
       component.doSomthing();
      var Dialog = function(){
          ...
      }
      return {
          Dialog: Dialog
     }
 });

注:require接收两个参数,第一个参数是数组,含需加载的模块,第二个参数是回调,在模块加载成功后执行,且回调中的参数对应模块的加载顺序,这样主程序只需要加载一个JS文件,Dialog.js即可,其自会去加载event.js等文件,event.js再用同样的方式去加载base.js等,使用者无需考虑Dialog加载了多少引用,也无法担心其使用后会增减依赖。

###六、CMD

以sea.js为代表,官网 http://seajs.org/docs/,理念为需要时再执行,即依赖就近

 =============main.js===================
 <script src="js/sea.js"></script>    
<script>seajs.use("../static/hello/src/main")</script>

==============Dialog.js=======================
define( function( require, exports, module ){
    var event = require( 'event' );
    event.doSomthing();
    var bootstrap = require( 'bootstrap' );
    bootstrap.doSomthing();
    var component = require( 'component' );
    component.doSomthing();
    var Dialog = function(){
         ...
    }                    
     exports.Dialog = Dialog;
})

注:模块引用本身就在回调里,并且在使用前引用即可,所以sea.js强调的是懒加载,这也就是AMD和CMD之间唯一的区别。实际上,require.js也支持CMD的写法,不过依赖前置是其官方推荐。我最初因为对国产和玉伯的支持敬佩,使用了seajs,近来为了弄清楚AMD,项目全部用了requirejs,虽然网上及seajs官方对两种逻列了一些分别,但使用下来,感觉并无优劣。

###七、UMD

因服务端与客户端对模块化加载方式的不同,就会人想做一个兼容的模块UMD(Universal Module Definition),来解决跨平台的问题。但对于我们开发者来讲实则没有什么意义,也不深入了。

###八、require.js 使用

require.config({
      baseUrl: 'module/js',     //配置path下的统一目的,以便path中不会分别配置
      path: {                              //配置module路径,以便引用时不用写全路径
          module1: 'module1',
          module2: 'module2',
          module3: 'http://www.site.com/module3'     //若引用的是第三方,可以全部全路径
     },
      shim: {     //对不符合AMD规则的模块引用
          'underscore': {
               exports: '_'          //配置其原对外接口
          },
          'backbone': {
               deps: ['underscore', 'jquery'],     //配置其依赖项
               exports: 'Backbone'
         }
     }
 });

define( ['module1', 'module2' ], function( module1, module2 ){
      module1.doSomthing();
      module2.doSomthing();
      function module3(){
          ...
     }
      return {
          module3: module3
     }
 })

###九、require.js 时间戳

require.js没有找到可配置时间戳,可修改require.js源文件中的load方法

 load: function (id, url) {
    url += '?' + timestamp;     
    req.load(context, id, url);     
},

新增 url += ‘?’ + timestamp; 这一处语句,即为url添加timestamp,timestamp可以另外通过配置文件配置。我使用nodejs开发,在服务端的config文件配置 timestamp: ‘2015091112’,将其插入到客户端 ejs文件内

 <script>
     var timestamp = '<%= timestamp %>';
 </script>

这样即可在 require.js 文件内调用。

###十、require.js 合并 官网提供了r.js用于该模块化的打包 https://github.com/jrburke/r.js r.js是将通过require.js引用的模块化文件都合并到一个文件里,以减少文件加载数量。 项目参考 https://github.com/xiaolulu/mynodejs.git 首先下载r.js,可以到静态文件根目录,新建build.js用于配置r.js,常见的配置如下

build.js

({ 
    appDir: './', //项目根目录,给后面的baseUrl做参考;非必要项;默认为与r.js平级目录
    dir: './min', //压缩后的文件的输出目录 
    baseUrl: './', //模块相对目录,与后面的paths配置使用
    modules: [ 
            { name:'login'},
            { name: 'register' },
            { name: 'center' }
    ], 
    fileExclusionRegExp: /^(r|build)\.js|.*\.scss$/, //过滤文件不压缩
    optimizeCss: 'standard', //css的标准方式
    optimize: 'uglify', //设置是否压缩;uglify(压缩格式)|none(只合并不压缩)
    removeCombined: true, //设置移除压缩后的文件
    paths: { //设置模块别名,方便引用,压缩后require.js引用的模块路径就在这里定义的,不是require.config里的
            md5: 'core/js/md5',
            validate: 'widget/validate/validate',
            all: 'public/js/all',
            login: 'module/issue/login/login',
            register: 'module/issue/register/register',
            center: 'module/user/center/center'
    }
})

shell执行 node r.js -o build.js

###十一、almond

合并后所有原通过模块化加载的文件都已经在一个文件里了,已经不需要require.js的大部分功能,但因合并后代码里还是有require / define的函数定义,这里命令解析还是要有的,所以就有了almond.js(约15KB),其保留了require.js(约80KB)的子集,但不支持模块化加载,即对已经合并后的模块解析可以使用,减少代码量。

不过,其理念是好的,但实现方案还待改进,其执行后就会almond.js与前面合并后过的业务JS合并成一个文件,直接用 src来引用,意味着每个页面都需要的 almond.js将无法缓存,所以当业务JS超过五个时,这个方案将没有意义,有兴趣可以自行到git上学习,这里不在讲述 https://github.com/jrburke/almond

2 回复

恩~~~写的挺详细的,不过现在这些模块加载方式显得有点落伍了,由于react的组件化开发热潮带动了webpack这种新型的模块思想,不妨LZ可以试下

回到顶部