当前位置: 首页 > 科技观察

RequireJS模块化编程详解

时间:2023-03-19 23:23:35 科技观察

1.模块化编写模块化编程一般有几个过渡过程,如下所述。原方法functionm1(){  //...}functionm2(){  //...}以上函数m1()和m2()组成一个模块。使用时直接调用即可。这种做法的缺点很明显:全局变量被“污染”,不能保证不会与其他模块发生变量名冲突,模块成员之间没有直接关系。对象的写法为了解决上述缺点,可以将模块写成一个对象,所有的模块成员都放在这个对象中。varmodule1=newObject({    _count:0,    m1:function(){      //...    },    m2:function(){      /...    }  });以上函数m1()和m2()封装在module1对象中。使用的时候,就是调用这个对象的属性。module1.m1();但是这种写法会暴露所有的模块成员,外部可以重写内部状态。例如,外部代码可以直接更改内部计数器的值。module1._count=5;Immediately-InvokedFunctionExpression使用“Immediately-InvokedFunctionExpression”(IIFE),可以达到不暴露私有成员的目的。varmodule1=(function(){    var_count=0;    varm1=function(){      //...    };    varm2=function(){      //...    };    返回{      m1:m1,      m2:m2    };  })();使用上面的写法,外部代码无法读取内部的_count变量。console.info(module1._count);//undefinedmodule1是编写Javascript模块的基本方式。下面我们来处理一下这个写法。扩充方式如果一个模块很大,必须分成几个部分,或者一个模块需要继承另一个模块,那么就需要使用“扩充”。varmodule1=(function(mod){    mod.m3=function(){      //...    };    returnmod;  })(module1);上面的代码是module1模块添加了一个新的方法m3(),然后返回新的module1模块。松散扩充在浏览器环境下,模块的各个部分通常是从网上获取的,有时无法知道先加载哪个部分。如果使用上一节的写法,最先执行的部分可能会加载一个不存在的空对象,这时就必须使用“widezoommode”。varmodule1=(function(mod){//...returnmod;})(window.module1||{});与“放大模式”相比,“宽幅放大模式”是“立即执行函数”的参数,可以为空对象。输入全局变量的独立性是模块的一个重要特性,模块内部绝不能直接与程序的其他部分进行交互。为了调用模块内的全局变量,必须将其他变量显式输入到模块中。varmodule1=(function($,YAHOO){    //...  })(jQuery,YAHOO);上面的module1模块需要用到jQuery库和YUI库,所以这两个库(实际上是两个模块)作为参数输入module1。这样除了保证模块的独立性,也使得模块之间的依赖关系明显。2.AMD规范2009年,美国程序员RyanDahl创建了node.js项目,使用javascript语言进行服务器端编程。这标志着“Javascript模块化编程”的正式诞生。因为说实话,在浏览器环境下,没有模块并不是什么特别大的问题,毕竟web程序的复杂度是有限的;但是在服务器端,必须要有与操作系统和其他应用交互的模块,否则就没法编程了。node.js的模块系统是参考CommonJS规范实现的。在CommonJS中,有一个全局方法require()用于加载模块。假设有一个数学模块math.js,可以按如下方式加载。varmath=require('数学');然后,就可以调用模块提供的方法:varmath=require('math');math.add(2,3);//5因为这个系列主要是浏览器编程,没有涉及到Node.js,所以关于CommonJS就不多介绍了。这里我们只需要知道require()是用来加载模块的。既然您有了服务器端模块,那么人们自然需要客户端模块。而且两者是兼容的,一个模块不需要修改,既可以运行在服务器端,也可以运行在浏览器端。但是,CommonJS规范由于存在重大限制而不适用于浏览器环境。还是上一节的代码,如果在浏览器中运行,就会出大问题,能看出来吗?varmath=require('数学');math.add(2,3);第二行math.add(2,3)在第一行require('math')之后运行,因此它必须等待math.js完成加载。也就是说,如果加载时间很长,整个应用程序就会坐在那里等待。这在服务器端是没有问题的,因为所有的模块都存储在本地硬盘中,可以同步加载,等待的时间就是硬盘的读取时间。但是,对于浏览器来说,这是一个很大的问题,因为模块是放在服务器端的,等待时间取决于网速,可能需要很长时间,浏览器处于“假死”状态.因此,浏览器端的模块不能使用“同步加载”(synchronous),只能使用“异步加载”(asynchronous)。这就是AMD规范诞生的背景。AMD是“AsynchronousModuleDefinition”的缩写,意思是“异步模块定义”。它异步加载模块,模块的加载不影响后面语句的运行。所有依赖该模块的语句都定义在一个回调函数中,回调函数只有在加载完成后才会运行。AMD也使用require()语句来加载模块,但与CommonJS不同的是,它需要两个参数:require([module],callback);最后一个参数[module]是一个数组,里面的成员是要加载的模块;第二个参数callback为加载成功后的回调函数。如果将之前的代码改写成AMD形式,则如下:require(['math'],function(math){math.add(2,3);});math.add()和math模块加载不同步是的,浏览器不会卡顿。所以很明显,AMD更适合浏览器环境。3、require.js的加载有人可能会认为加载这个文件也可能导致网页失去响应。有两种解决方法,一种是在网页底部加载,另一种是这样写:async属性表示该文件需要异步加载,防止页面无响应。IE不支持这个属性,只支持defer,所以也写defer。加载完require.js,接下来就是加载我们自己的代码了。假设我们自己的代码文件是main.js,也是放在js目录下。那么,只需要这样写:data-main属性的作用是指定主模块网络程序。上面的例子中是js目录下的main.js,这个文件会被require.js先加载。由于require.js默认的文件扩展名是js,所以main.js可以简写为main。require.config()的配置使用require.config()方法,我们可以自定义模块的加载行为。require.config()写在主模块(main.js)的头部。参数是一个对象,这个对象的paths属性指定了各个模块的加载路径。require.config({    baseUrl:"js/lib",    paths:{?"jquery":"jquery.min",      "underscore":"underscore.min","主干":"backbone.min"    }  });必须使用特定的define()函数定义AMD模块。如果一个模块不依赖其他模块,可以直接在define()函数中定义。假设有一个math.js文件,它定义了一个数学模块。然后,math.js应该这样写:      返回{      add:add    };  });加载方法如下://main.js  require(['math'],function(math){    alert(math.add(1,1));  });如果这个模块还依赖于其他模块,那么define()函数的第一个参数必须是一个数组,表示该模块的依赖关系。define(['myLib'],function(myLib){    functionfoo(){      myLib.doSomething();    }    return{      foo:foo    };  });当require()函数加载上述模块时,会首先加载myLib.js文件。define()的完整定义:define('sample3',['sample','sample1'],function(sample,sample1){varsample4=require('sample4');returnfunction(){alert(sample.name+':'+sample.sayhell());}});关于define函数名与require函数依赖名的关系1)define(name,[],callback);这个名字可以省略,默认就是文件名;当然也可以自定义。一旦我们定义了名称,根据源码我们可以发现,define函数实际上是将名称、依赖模块、回调函数作为一个对象存储在全局数组中,即defQueue.push([name,deps,打回来]);那么这个名字就是这个组件的注册ID!2)要求([姓名,姓名2],回调);系统会先检查全文搜索路径中是否有对应的路径,如果没有,会自动拼接baseUrl上的路径异步加载js文件,加载时从源码可以看到,vardata=getScriptData(事件);返回的data.id其实就是name,然后执行contex.completeLoad(node.id),里面很清楚,比较define中注册的name和这里获取的name,相等则执行,所以原因就是:require和define的名字一定要一致!加载标签后,获取标签的唯一标识名称,加载非标准模块,比如underscore和backbone这两个库,就没有使用AMD规范编写。如果要加载它们,必须首先定义它们的特性。require.config({    shim:{        '下划线':{        exports:'_'      },?'骨干':{        deps:['下划线','jquery'],        exports:'Backbone'      }    }  });require.config()接受一个配置对象,这个对象有前面说的paths属性,还有一个shim属性,专门用于配置不兼容的模块。具体来说,每个模块都必须定义(1)exportsvalue(输出变量名),表示被外部调用时模块的名称;(2)deps数组,表示模块的依赖关系。例如,一个jQuery插件可以这样定义:}  }require.js插件require.js也提供了一系列的插件来实现一些特定的功能。domready插件允许回调函数在页面DOM结构加载后运行。require(['domready!'],function(doc){    //calledoncetheDOMisready  });文本和图像插件允许require.js加载文本和图像文件。define([    'text!review.txt',    'image!cat.jpg'    ],    function(review,cat){?console.log(review);      document.body.appendChild(猫);    }  );类似的插件还有json和mdown,分别用于加载json文件和markdown文件。参考地址:Javascript模块化编程(一):模块编写Javascript模块化编程(二):AMD规范Javascript模块化编程(三):require.js的用法RequireJS定义详解