JavaScript模块化:使用requireJS按需加载

模块加载器的概念可能稍微接触过前端开发的童鞋都不会陌生,通过模块加载器可以有效的解决这些问题:

  1. JS文件的依赖关系。
  2. 通过异步加载优化script标签引起的阻塞问题
  3. 可以简单的以文件为单位将功能模块化并实现复用

主流的JS模块加载器有requireJSSeaJS等,加载器之间可能会因为遵循的规范不同有微妙的差别,从纯用户的角度出发,之所以选requireJS而不是SeaJS主要是因为:

  • 功能实现上两者相差无几,没有明显的性能差异或重大问题。
  • 文档丰富程度上,requireJS远远好于SeaJS,就拿最简单的加载jQuery和jQuery插件这回事,虽然两者的实现方法相差无几, 但requireJS就有可以直接拿来用的Demo,SeaJS还要读文档自己慢慢折腾。一些问题的解决上,requireJS为关键词也更容易找到答 案。

requireJS 加载jQuery + jQuery插件

可能对于一般Web App来说,引入jQuery及相关插件的概率是最大的,requireJS也亲切的给出了相应的解决方案及动态加载jQuery及插件的文档及实例代码

在最新的jQuery1.9.X中,jQuery已经在最后直接将自己注册为一个AMD模块,即是说可以直接被requireJS作为模块加载。如果是加载旧版的jQuery有两种方法:

1. 让jQuery先于requireJS加载

2. 对jQuery代码稍做一点处理,在jQuery代码包裹一句:

  1. define(["jquery"], function($) { 
  2.     // $ is guaranteed to be jQuery now */ 
  3. }); 

requireJS的示例中,直接将requireJS与jQuery合并为一个文件,如果是采用jQuery作为核心库的话推荐这种做法。

同样对于jQuery插件来说也有两种方法

1. 在插件外包裹代码

  1. define(["jquery"], function($){ 
  2.      // Put here the plugin code.  
  3. }); 

2. 在使用reuqireJS代码加载前注册插件(比如在main.js)中

  1. requirejs.config({ 
  2.     "shim": { 
  3.         "jquery-cookie"  : ["jquery"
  4.     } 
  5. }); 

requireJS加载第三方类库

在实例的App中还用到了jQuery以外的第三方类库,如果类库不是一个标准的AMD模块而又不想更改这些类库的代码,同样需要提前进行定义:

  1. require.config({ 
  2.       paths: { 
  3.             'underscore''vendor/underscore' 
  4.       }, 
  5.       shim: { 
  6.           underscore: { 
  7.               exports: '_' 
  8.           } 
  9.       } 
  10. }); 

CSS文件的模块化处理

在requireJS中,模块的概念仅限于JS文件,如果需要加载图片、JSON等非JS文件,requireJS实现了一系列加载插件

但是遗憾的是requireJS官方没有对CSS进行模块化处理,而我们在实际项目中却往往能遇到一些场景,比如一个轮播的图片展示栏,比如高级编辑器等等。几乎所有的富UI组件都会由JS与CSS两部分构成,而CSS之间也存在着模块的概念以及依赖关系。

为了更好的与requireJS整合,这里采用require-css来解决CSS的模块化与依赖问题。

require-css是一个requireJS插件,下载后将css.jsnormalize.js放于main.js同级即可默认被加载,比如在我们的项目中需要加载jQuery Mobile的css文件,那么可以直接这样调用:

  1. require(['jquery''css!../css/jquery.mobile-1.3.0.min.css'], function($) { 
  2. }); 

不过由于这个CSS本质上是属于jQuery Mobile模块的一部分,更好的做法是将这个CSS文件的定义放在jQuery Mobile的依赖关系中,最终我们的requireJS定义部分为:

  1. require.config({ 
  2.       paths: { 
  3.             'jquerymobile''vendor/jquery.mobile-1.3.0'
  4.             'jstorage' : 'vendor/jstorage'
  5.             'underscore''vendor/underscore' 
  6.       }, 
  7.       shim: { 
  8.           jquerymobile : { 
  9.             deps: [ 
  10.                 'css!../css/jquery.mobile-1.3.0.min.css' 
  11.             ] 
  12.           }, 
  13.           underscore: { 
  14.               exports: '_' 
  15.           } 
  16.       } 
  17. }); 

在使用模块时,只需要:

  1. require(['jquery''underscore''jquerymobile''jstorage'], function($, _) { 
  2. }); 

jQuery Mobile的CSS文件就会被自动加载,这样CSS与JS就被整合为一个模块了。同理其他有复杂依赖关系的模块也可以做类似处理,requireJS会解决依赖关系的逻辑。

数据源的加载与等待

Web App一般都会动态加载后端的数据,数据格式一般可以是JSON、JSONP也可以直接是一个JS变量。这里以JS变量为例:

  1. var restaurants = [ 
  2.     { 
  3.         "name""KFC" 
  4.     }, 
  5.     { 
  6.         "name""7-11" 
  7.     }, 
  8.     { 
  9.         "name""成都小吃" 
  10.     } 

载入这段数据:

  1. $.getScript('data/restaurants.json'function(e){ 
  2.     var data = window.restaurants; 
  3.     alert(data[0].name); //KFC 
  4. }); 

单一的数据源确实很简单,但是往往一个应用中会有多个数据源,比如在这个实例App中UI就需要载入用户信息、餐厅信息、订餐信息三种数据后才能工作。如果仅仅靠多层嵌套回调函数的话,可能代码的耦合就非常重了。

为了解决多个数据加载的问题,我习惯的解决方法是构造一个dataReady事件响应机制。

  1. var foodOrder = { 
  2.  
  3.     //数据载入后要执行的函数暂存在这里 
  4.     dataReadyFunc : [] 
  5.  
  6.     //数据源URL及载入状态 
  7.     , dataSource : [ 
  8.         { url : 'data/restaurants.json', ready : false, data : null }, 
  9.         { url : 'data/users.json', ready : false, data : null }, 
  10.         { url : 'data/foods.json', ready : false, data : null } 
  11.     ] 
  12.  
  13.     //检查数据源是否全部载入完毕 
  14.     , isReady : function(){ 
  15.         var isReady = true
  16.         for(var key in this.dataSource){ 
  17.             if(this.dataSource[key].ready !== true){ 
  18.                 isReady = false
  19.             } 
  20.         } 
  21.         return isReady; 
  22.     } 
  23.  
  24.     //数据源全部加载完毕,则逐一运行dataReadyFunc中存放的函数 
  25.     , callReady : function(){ 
  26.         if(true === this.isReady()){ 
  27.             for(var key in this.dataReadyFunc){ 
  28.                 this.dataReadyFunc[key](); 
  29.             } 
  30.         } 
  31.     } 
  32.  
  33.     //供外部调用,会将外部输入的函数暂存在dataReadyFunc中 
  34.     , dataReady : function(func){ 
  35.         if (typeof func !== 'function') { 
  36.             return false
  37.         }  
  38.         this.dataReadyFunc.push(func); 
  39.     } 
  40.  
  41.     , init : function(){ 
  42.         var self = this
  43.         var _initElement = function(key, url){ 
  44.             $.getScript(url, function(e){ 
  45.                 //每次载入数据后,将数据存放于dataSource中,将ready状态置为true,并调用callReady 
  46.                 self.dataSource[key].data = window[key]; 
  47.                 self.dataSource[key].ready = true
  48.                 self.callReady(); 
  49.             }); 
  50.         } 
  51.         for(var key in this.dataSource){ 
  52.             _initElement(key, this.dataSource[key].url); 
  53.         } 
  54.     } 

用法为:

  1. foodOrder.dataReady(function(){ 
  2.    alert(1);      
  3. }); 
  4. foodOrder.init(); 

dataReady内的alert将会在所有数据载入完毕后开始执行。

这段处理的逻辑并不复杂,将所有要执行的方法通过dataReady暂存起来,等待数据全部加载完毕后再执行,更加复杂的场景此方法仍然通用。

展开阅读全文

没有更多推荐了,返回首页