<script type="text/javascript">var a = 1;var b = 2;var c = a * a + b * b;if(c> 1) {alert("c > 1");}function add(a, b) {return a + b;}c = add(a,b);</script><a href="javascript:;" onclick="click(this);" title="">请点击</a>这些代码的特点是:
//module.jsvar student = function (name) {return name && {getName: function () {return name;}};},course = function (name) {return name && {getName: function () {return name;}}},controller = function () {var data = {};return {add: function (stu, cour) {var stuName = stu && stu.getName(),courName = cour && cour.getName(),current,_filter = function (e) {return e === courName;};if (!stuName || !courName) return;current = data[stuName] = data[stuName] || [];if (current.filter(_filter).length === 0) {current.push(courName);}},list: function (stu) {var stuName = stu && stu.getName(),current = data[stuName];current && console.log(current.join(";"));}}};//main.jsvar stu = new student("lyzg"),c = new controller();c.add(stu,new course("javascript"));c.add(stu,new course("html"));c.add(stu,new course("css"));c.list(stu);以上代码定义了三个模块分别表示学生,课程和控制器,然后在main.js中调用了controller提供的add和list接口,为lyzg这个学生添加了三门课程,然后在控制台显示了出来。运行结果如下:javascript;html;css通过上例,可以看出模块化的代码结构和逻辑十分清晰,代码看起来十分优雅,另外由于逻辑都通过模块拆分,所以达到了解耦的目的,代码的功能也会比较健壮。不过上例使用的这种模块化开发方式也并不是没有问题,这个问题就是它还是把模块引用如student这些直接添加到了全局空间下,虽然通过模块减少了很多全局空间的变量和函数,但是模块引用本身还是要依赖全局空间,才能被调用,当模块较多,或者有引入第三方模块库时,仍然可能造成命名冲突的问题,所以这种全局空间下的模块化开发的方式并不是最完美的方式。目前常见的模块化开发方式,全局空间方式是最基本的一种,另外常见的还有遵循AMD规范的开发方式,遵循CMD规范的开发方式,和ECMAScript 6的开发方式。需要说明的是,CMD和ES6跟本文的核心没有关系,所以不会在此介绍,后面的内容主要介绍AMD以及实现了AMD规范的RequireJS。
require([module], callback)其中:[module]:是一个数组,里面的成员就是要加载的模块;callback:是模块加载完成之后的回调函数所有遵循AMD规范的模块化工具,都必须按照它的要求去实现,比如RequireJS这个库,就是完全遵循AMD规范实现的,所以在利用RequireJS加载或者调用模块时,如果你事先知道AMD规范的话,你就知道该怎么用RequireJS了。规范的好处在于,不同的实现却有相同的调用方式,很容易切换不同的工具使用,至于具体用哪一个实现,这就跟各个工具的各自的优点跟项目的特点有关系,这些都是在项目开始选型的时候需要确定的。目前RequireJS不是唯一实现了AMD规范的库,像Dojo这种更全面的js库也都有AMD的实现。
(function() {var s = document.createElement("script");s.type = "text/javascript";s.async = true;s.src = "http://yourdomain.com/script.js";var x = document.getElementsByTagName("script")[0];x.parentNode.insertBefore(s, x);})();这段代码,放置在script标记内部,然后该script标记添加到body元素的底部即可。<script defer async="true" type="text/javascript" src="app/foo.js"></script><script defer async="true" type="text/javascript" src="app/bar.js"></script><script defer async="true" type="text/javascript" src="app/main.js"></script>这种方式下,所有异步js在执行的时候还是按顺序执行的,不然就会存在依赖问题,比如如果上例中的main.js依赖foo.js和bar.js,但是main.js先执行的话就会出错了。虽然从来理论上这种方式也算不错了,但是不够好,因为它用起来很繁琐,而且还有个问题就是页面需要添加多个script标记以及没有办法完全做到按需加载。
<script defer async="true" src="/bower_components/requirejs/require.js"></script>由于这里用到了defer和async这两个异步加载的属性,所以require.js是异步加载的,你把这个script标记放置在任何地方都没有问题。
<script data-main="scripts/main.js" defer async="true" src="/bower_components/requirejs/require.js"></script>对比4.01,你会发现script标记多了一个data-main,RJ用这个配置当前页面的主JS,你要把逻辑都写在这个main.js里面。当RJ自身加载执行后,就会再次异步加载main.js。这个main.js是当前网页所有逻辑的入口,理想情况下,整个网页只需要这一个script标记,利用RJ加载依赖的其它文件,如jquery等。

main.js是跟当前页面相关的主JS,app文件夹存放本项目自定义的模块,lib存放第三方库。
html中按4.02的方式配置RJ。main.js的代码如下:
require(["lib/foo", "app/bar", "app/app"], function(foo, bar, app) {//use foo bar app do sth});在这段JS中,我们利用RJ提供的require方法,加载了三个模块,然后在这个三个模块都加载成功之后执行页面逻辑。require方法有2个参数,第一个参数是数组类型的,实际使用时,数组的每个元素都是一个模块的module ID,第二个参数是一个回调函数,这个函数在第一个参数定义的所有模块都加载成功后回调,形参的个数和顺序分别与第一个参数定义的模块对应,比如第一个模块时lib/foo,那么这个回调函数的第一个参数就是foo这个模块的引用,在回调函数中我们使用这些形参来调用各个模块的方法,由于回调是在各模块加载之后才调用的,所以这些模块引用肯定都是有效的。require.config({baseUrl: "scripts"});这个配置必须放置在main.js的最前面。data-main与config配置同时存在的时候,以config为准,由于RJ的其它配置也是在这个位置配置的,所以4.03中的main.js可以改成如下结构,以便将来的扩展:require.config({baseUrl: "scripts"});require(["lib/foo", "app/bar", "app/app"], function(foo, bar, app) {// use foo bar app do sth});关于module ID,就是在require方法以及后续的define方法里,用在依赖数组这个参数里,用来标识一个模块的字符串。上面代码中的["lib/foo", "app/bar", "app/app"]就是一个依赖数组,其中的每个元素都是一个module ID。值得注意的是,module ID并不一定是该module 相关JS路径的一部分,有的module ID很短,但可能路径很长,这跟RJ的解析规则有关。下一节详细介绍。require.config({baseUrl: "scripts"});require(["/lib/foo", "test.js", "/js/jquery"], function(foo, bar, app) {// use foo bar app do sth});这三个module 都不会根据baseUrl + module ID的规则来解析,而是直接用module ID来解析,等效于下面的代码:<script src="/lib/foo.js"></script><script src="test.js"></script><script src="/js/jquery.js"></script>各种module ID解析举例:

main.js如下:
require.config({baseUrl: "scripts"});require(["lib/foo", "app/bar", "app/app"], function(foo, bar, app) {// use foo bar app do sth});baseUrl为:scripts目录scripts/lib/foo.jsscripts/app/bar.jsscripts/app/app.js例2,项目结构同例1:
require.config({baseUrl: "scripts/lib",paths: { app: "../app"}});require(["foo", "app/bar", "app/app"], function(foo, bar, app) {// use foo bar app do sth});这里出现了一个新的配置paths,它的作用是针对module ID中特定的部分,进行转义,如以上代码中对app这个部分,转义为../app,这表示一个相对路径,相对位置是baseUrl所指定的目录,由项目结构可知,../app其实对应的是scirpt/app目录。正因为有这个转义的存在,所以以上代码中的app/bar才能被正确解析,否则还按baseUrl + moduleID的规则,app/bar不是应该被解析成scripts/lib/app/bar.js吗,但实际并非如此,app/bar被解析成scripts/app/bar.js,其中起关键作用的就是paths的配置。通过这个举例,可以看出module ID并不一定是js文件路径中的一部分,paths的配置对于路径过程的js特别有效,因为可以简化它的module ID。define(["./bar"], function(bar) { return { doSth: function() { bar.doSth(); } }});上面的代码通过define定义了一个模块,这个define函数后面介绍如何定义模块的时候再来介绍,这里简单了解。这里这种用法的第一个参数跟require函数一样,是一个依赖数组,第二个参数是一个回调,也是在所有依赖加载成功之后调用,这个回调的返回值会成为这个模块的引用被其它模块所使用。<script data-main="scripts/main" src="scripts/require.js"></script><script src="scripts/other.js"></script>//main.jsrequire.config({paths: {foo: "libs/foo-1.1.3"}});//other.jsrequire( ["foo"], function( foo ) {//foo is undefined});由于main.js是异步加载的,所以other.js会比它先加载,但是RJ的配置存在于main.js里面,所以在加载other.js读不到RJ的配置,在other.js执行的时候解析出来的foo的路径就会变成scripts/foo.js,而正确路径应该是scripts/libs/foo-1.1.3.js。define(["require", "app/bar", "app/app"], function(require) {var bar= require("app/bar");var app= require("app/app");//use bar and app do sth});上面的代码,在callback定义的时候,只用了一个形参,这主要是为了减少形参的数量,避免整个回调的签名很长。依赖的模块在回调内部可以直接用require(moduleID)的参数得到,由于在回调执行前,依赖的模块已经加载,所以此处调用不会再重新加载。但是如果此处获取一个并不在依赖数组中出现的module ID,require很有可能获取不到该模块引用,因为它可能需要重新加载,如果它没有在其它模块中被加载过的话。
4.08 使用define定义模块
AMD规定的模块定义规范为:
define(id?, dependencies?, factory);其中:id: 模块标识,可以省略。dependencies: 所依赖的模块,可以省略。factory: 模块的实现,或者一个JavaScript对象关于第一个参数,本文不会涉及,因为RJ建议所有模块都不要使用第一个参数,如果使用第一个参数定义的模块成为命名模块,不适用第一个参数的模块成为匿名模块,命名模块如果更名,所有依赖它的模块都得修改!第二个参数是依赖数组,跟require一样,如果没有这个参数,那么定义的就是一个无依赖的模块;最后一个参数是回调或者是一个简单对象,在模块加载完毕后调用,当然没有第二个参数,最后一个参数也会调用。

1. 定义简单对象模块:
app/bar.js
define({ bar:"I am bar."});利用main.js测试:require.config({baseUrl: "scripts/lib",paths: {app: "../app"}});require(["app/bar"], function(bar) {console.log(bar);// {bar: "I am bar."}});2. 定义无依赖的模块:define(function () {return {nodec: "yes, I don"t need dependence."}});利用main.js测试:require.config({baseUrl: "scripts/lib",paths: {app: "../app"}});require(["app/nodec"], function(nodec) {console.log(nodec);// {nodec: yes, I don"t need dependence."}});3. 定义依赖其它模块的模块:define(["jquery"], function($){//use $ do sth ...return {useJq: true}});利用main.js测试:require.config({baseUrl: "scripts/lib",paths: {app: "../app"}});require(["app/dec"], function(dec) {console.log(dec);//{useJq: true}});4. 循环依赖:define(["foo"],function(foo){ return { name: "bar", hi: function(){console.log("Hi! " + foo.name); } }});lib/foo.js:define(["app/bar"],function(bar){ return { name: "foo", hi: function(){console.log("Hi! " + bar.name); } }});利用main.js测试:require.config({baseUrl: "scripts/lib",paths: {app: "../app"}});require(["app/bar", "foo"], function(bar, foo) {bar.hi();foo.hi();});运行结果:
如果改变main.js中require部分的依赖顺序,结果:

循环依赖导致两个依赖的module之间,始终会有一个在获取另一个的时候,得到undefined。解决方法是,在定义module的时候,如果用到循环依赖的时候,在define内部通过require重新获取。main.js不变,bar.js改成:
define(["require", "foo"], function(require, foo) {return {name: "bar",hi: function() { foo = require("foo");console.log("Hi! " + foo.name);}}});foo.js改成:define(["require", "app/bar"], function(require, bar) {return {name: "foo",hi: function() { bar = require("app/bar");console.log("Hi! " + bar.name);}}});利用上述代码,重新执行,结果是:
模块定义总结:不管模块是用回调函数定义还是简单对象定义,这个模块输出的是一个引用,所以这个引用必须是有效的,你的回调不能返回undefined,但是不局限于对象类型,还可以是数组,函数,甚至是基本类型,只不过如果返回对象,你能通过这个对象组织更多的接口。
4.09 内置的RJ模块
再看看这个代码:
define(["require", "app/bar"], function(require) {return {name: "foo",hi: function() {var bar = require("app/bar");console.log("Hi! " + bar.name);}}});依赖数组中的require这个moduleID对应的是一个内置模块,利用它加载模块,怎么用你已经看到了,比如在main.js中,在define中。另外一个内置模块是module,这个模块跟RJ的另外一个配置有关,具体用法请在第5大部分去了解。define(["require"], function(require) {var cssUrl = require.toUrl("./style.css");});这个功能在你想要动态地加载一些文件的时候有用,注意要使用相对路径。require.config({baseUrl: "scripts/lib",paths: {app: "../app"},shim: { underscore: { exports: "_" }}});require(["underscore"], function(_) {// 现在可以通过_调用underscore的api了});如你所见,RJ在shim中添加了一个对underscore这个模块的配置,并通过exports属性指定该模块暴露的全局变量,以便RJ能够对这些模块统一管理。require.config({baseUrl: "scripts/lib",paths: {app: "../app"},shim: { backbone: {deps: ["underscore", "jquery"],exports: "Backbone" }}});require(["backbone"], function(Backbone) {//use Backbone"s API});由于backbone这个组件依赖jquery和underscore,所以可以通过deps属性配置它的依赖,这样backbone将会在另外两个模块加载完毕之后才会加载。requirejs.config({shim: {"jquery.colorize": {deps: ["jquery"],exports: "jQuery.fn.colorize"},"jquery.scroll": {deps: ["jquery"],exports: "jQuery.fn.scroll"},"backbone.layoutmanager": {deps: ["backbone"]exports: "Backbone.LayoutManager"}}}); 5.02 configrequirejs.config({config: {"bar": {size: "large"},"baz": {color: "blue"}}});如你所见,config属性中的bar这一节是在用于module ID为bar这个模块的,baz这一节是用于module ID为baz这个模块的。具体使用以bar.js举例:define(["module"], function(module) {//Will be the value "large"var size = module.config().size;});前面提到过,RJ的内置模块除了require还有一个module,用法就在此处,通过它可以来加载config的内容。require.config({ enforceDefine: true,baseUrl: "scripts/lib",paths: {app: "../app"},shim: { backbone: { deps: ["underscore", "jquery"],exports: "Backbone" }}});require(["backbone"], function(Backbone) {console.log(Backbone);});把最后三行改成:define(["backbone"], function(Backbone) {console.log(Backbone);});才不会报错。requirejs.config({//To get timely, correct error triggers in IE, force a define/shim exports check.enforceDefine: true,paths: {jquery: ["http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min",//If the CDN location fails, load from this location"lib/jquery"]}});//Laterrequire(["jquery"], function ($) {});上述代码先尝试加载CDN版本,如果出错,则退回到本地的lib/jquery.js。requirejs.onError = function (err) {console.log(err.requireType);if (err.requireType === "timeout") {console.log("modules: " + err.requireModules);}throw err;};以上就是本文的全部内容,希望对大家的学习有所帮助。