分享

使用requireJS加载不符合AMD规范的js文件:shim的使用方式和实现原理

 KILLKISS 2016-05-31

我们知道在javascript中定义全局变量有2种方式,本质上是等价的,都是向window对象注入属性或者方法。

[javascript] view plain copy
在CODE上查看代码片派生到我的代码片
  1. // global.js  
  2.   
  3. var g_name = "aty";  
  4.   
  5. window.g_age = 25;  
当global.js加载的时候,浏览器的全局对象window就会多出2个属性:g_name和g_age。


我们编写一个js工具类或者是js框架,通常有2种方式:
方式1:dateUtil.js

[javascript] view plain copy
在CODE上查看代码片派生到我的代码片
  1. (function(window) {  
  2.    
  3.     var DateUtils = {};  
  4.       
  5.     DateUtils.toString = function(){  
  6.         alert("toString");  
  7.     };  
  8.    
  9.     // 全局变量  
  10.     window.DateUtils = DateUtils;  
  11.     
  12. })(window);  
这种方式是最常用的,比如JQuery、Underscore等框架都是采用这种结构编写的。

方式2:stringUtil.js

[javascript] view plain copy
在CODE上查看代码片派生到我的代码片
  1. // 全局变量  
  2. var StringUtils = {};  
  3.   
  4.   
  5. StringUtils.toUpperCase = function(input){  
  6.     alert("toUpperCase");  
  7. }  

很显然stringUtil.js和dateUtil.js都不符合AMD规范,现在我们看看如何通过requireJS进行加载。工程目录结构如下:
[plain] view plain copy
在CODE上查看代码片派生到我的代码片
  1. index.html  
  2. main.js  
  3. libs  
  4.     --dateUtil.js  
  5.     --stringUtil.js  


index.html和main.js的实现代码如下:

  1. <!doctype html>  
  2. <html>  
  3.     <head>  
  4.         <title>shim</title>  
  5.         <meta charset="utf-8">  
  6.         <script data-main="main.js" src="./../requirejs-2.1.15.js"></script>  
  7.     </head>  
  8.     <body>  
  9.         <div id="div1" style="width:200px;height:200px;background-color:#465ae0;"></div>  
  10.        
  11.     </body>  
  12. </html>  

[javascript] view plain copy
在CODE上查看代码片派生到我的代码片
  1. requirejs.config({  
  2.     baseUrl: 'libs'  
  3. });  
  4.   
  5.   
  6. require(["dateUtil","stringUtil"], function(dateUtil,stringUtil) {  
  7.       
  8.     alert(dateUtil===undefined);//true  
  9.   
  10. });  

运行index.html,通过F12观察:


很明显dateUtil.js和stringUtil.js能够被requireJS正常加载,但是不能获取到这2个模块的返回值。我们修改下index.html,给div注册事件处理函数,并在事件处理函数中调用stringUtil.js提供的方法:

  1. <!doctype html>  
  2. <html>  
  3.     <head>  
  4.         <title>shim</title>  
  5.         <meta charset="utf-8">  
  6.         <script data-main="main.js" src="./../requirejs-2.1.15.js"></script>  
  7.         <script>  
  8.             function test()  
  9.             {  
  10.                 StringUtils.toUpperCase();  
  11.             }  
  12.         </script>  
  13.     </head>  
  14.     <body>  
  15.         <div id="div1" style="width:200px;height:200px;background-color:#465ae0;" onclick="test();"></div>  
  16.        
  17.     </body>  
  18. </html>  
点击div1,可以发现test()函数不会报错。也就是说,requireJS加载不符合AMD规范的js文件,跟我们直接在html通过<script>标签加载,没有太大的差别。js文件中引入的全局变量,依然会存在,依然能够正常使用。


下面我们看下shim参数的使用方式,我们将main.js修改如下:

[javascript] view plain copy
在CODE上查看代码片派生到我的代码片
  1. requirejs.config({  
  2.     baseUrl: 'libs',  
  3.     shim:{  
  4.         dateUtil:{  
  5.               deps:[],  
  6.               exports: 'DateUtils'  
  7.         },  
  8.         stringUtil:{  
  9.               deps:[],  
  10.               exports: 'StringUtils'  
  11.         }  
  12.     }  
  13. });  
  14.   
  15.   
  16. require(["dateUtil","stringUtil"], function(dateUtil,stringUtil) {  
  17.       
  18.     stringUtil.toUpperCase();  
  19.     dateUtil.toString();  
  20.   
  21. });  
这段代码可以正常运行,可以看到:shim参数能够帮助我们以AMD模块的方式,使用那些不符合AMD规范的模块。下面接介绍下:deps和exports的含义。exports很好理解,就是模块的返回值。main.js中exports的值,一定要与dateUtil.js和stringUtil.js中暴露出的全局变量名称一致。很显然dateUtil.js和stringUtil.js这2个模块的返回值,就是暴露出的全局变量window.DateUtils和window.StringUtils,requireJS框架就是将这些全局变量的值返回,作为模块的返回结果。如果dateUtil.js或stringUtil.js中暴露了多个全局变量,那么exports可以指定其中任何的一个,作为模块的返回结果。不过一般的框架,都只会使用1个全局变量,这样冲突的可能性会减少,毕竟全局变量越少越好。

上面我们编写的dateUtil.js和stringUtil.js,都不依赖于其他js模块,所以指定的deps是空数组。下面我们编写的aplugin.js和bplugin.js都依赖于模块util.js。

[javascript] view plain copy
在CODE上查看代码片派生到我的代码片
  1. //aplugin.js  
  2. (function(window,util) {  
  3.    
  4.     var a = {};  
  5.       
  6.     a.toString = function(){  
  7.         alert("a="+util.add(1,2));  
  8.     };  
  9.    
  10.     // 全局变量  
  11.     window.a = a;  
  12.     
  13. })(window,util);  
[javascript] view plain copy
在CODE上查看代码片派生到我的代码片
  1. //bplugin.js  
  2.   
  3. var b = {};  
  4.   
  5. b.toString = function(){  
  6.     alert("b="+util.add(1,2));  
  7. }  
[javascript] view plain copy
在CODE上查看代码片派生到我的代码片
  1. //util.js  
  2. var util = {};  
  3.   
  4. util.add = function(v1,v2){  
  5.     return v1+v2;  
  6. };  
main.js代码如下,只有设置正确的依赖顺序,使用的时候才不会出问题。

[javascript] view plain copy
在CODE上查看代码片派生到我的代码片
  1. requirejs.config({  
  2.     baseUrl: 'libs',  
  3.     shim:{  
  4.         dateUtil:{  
  5.               deps:[],  
  6.               exports: 'DateUtils'  
  7.         },  
  8.         stringUtil:{  
  9.               deps:[],  
  10.               exports: 'StringUtils'  
  11.         },  
  12.         aplugin:{  
  13.               deps:["util"],  
  14.               exports: 'a'  
  15.         },  
  16.         bplugin:{  
  17.               deps:["util"],  
  18.               exports: 'b'  
  19.         }  
  20.     }  
  21. });  
  22.   
  23.   
  24. require(["stringUtil","dateUtil","aplugin","bplugin"], function(string,date) {  
  25.       
  26.     //string.toString();  
  27.     //date.toString();  
  28.     var aPl = require("aplugin");  
  29.     var bPl = require("bplugin");  
  30.     aPl.toString();  
  31.     bPl.toString();  
  32.   
  33. });  
很显然util.js也不符合AMD规范,如果A模块依赖于B模块,A模块不符合AMD规范(使用的是全局变量),那么B模块也必须是使用全局变量,否则会报错。即如果将util.js改成符合AMD规范的写法,那么aplugin.js和bplugin.js都会因找不到util对象而报错。

[javascript] view plain copy
在CODE上查看代码片派生到我的代码片
  1. // 符合AMD规范的util.js  
  2.   
  3. define(function(){  
  4.       
  5.     function add(v1,v2)  
  6.     {  
  7.         return v1+v2;  
  8.     }  
  9.       
  10.     return {"add":add};  
  11.   
  12. });  

最后我们看下shim配置参数中init的作用。init可以指定一个函数主要就是用来避免类库之间的冲突。由于不符合AMD规范的js文件,会使用全局变量。所以当加载多个模块的时候存在名字冲突的可能。比如JQuery、UnderScore等框架都会提供一个noConflict()函数来避免名字冲突,noConflict()的实现原理可以参考这篇文章

我们编写一个不符合AMD规范的模块conflict.js,使用了全局变量$E,并提供noConflict方法。

[javascript] view plain copy
在CODE上查看代码片派生到我的代码片
  1. (function(window) {  
  2.     // 保存之前数据  
  3.     var _$E = window.$E;  
  4.   
  5.     var myplugin = {"name":"aty"};  
  6.     myplugin.noConflict = function(){  
  7.         window.$E = _$E;  
  8.         return myplugin;  
  9.     };  
  10.       
  11.     // 向全局对象注册$E  
  12.     window.$E = myplugin;  
  13. })(window);  

将index.html修改如下,在requireJS加载之前,先定义一个全局变量$E。

  1. <!doctype html>  
  2. <html>  
  3.     <head>  
  4.         <title>shim</title>  
  5.         <meta charset="utf-8">  
  6.         <script>  
  7.             var $E = "before";  
  8.         </script>  
  9.         <script data-main="main.js" src="./../requirejs-2.1.15.js"></script>  
  10.     </head>  
  11.     <body>  
  12.         <div id="div1" style="width:200px;height:200px;background-color:#465ae0;" onclick="test();"></div>  
  13.     </body>  
  14. </html>  

main.js中代码如下:

[javascript] view plain copy
在CODE上查看代码片派生到我的代码片
  1. requirejs.config({  
  2.     baseUrl: 'libs',  
  3.     shim:{  
  4.         conflict:{  
  5.               deps:[],  
  6.               exports: '$E',  
  7.               init:function(){  
  8.                  return $E.noConflict();  
  9.               }  
  10.         }  
  11.     }  
  12. });  
  13.   
  14.   
  15. require(["conflict"], function(mayConflict) {  
  16.       
  17.     alert(mayConflict.name);  
  18.       
  19.     alert(window.$E);//before  
  20.       
  21. });  
运行index.html,可以发现conflict.js能够与之前定义的全局变量$E共存,避免了冲突,这就是通过init实现的。如果没有定义init,可以看到alert(window.$E)打印的值是undefined。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多