RequireJS和AMD规范

概述

RequireJS是一个工具库,主要用于客户端的模块管理。它可让客户端的代码分红一个个模块,实现异步或动态加载,从而提升代码的性能和可维护性。它的模块管理遵照AMD规范(Asynchronous Module Definition)。javascript

RequireJS的基本思想是,经过define方法,将代码定义为模块;经过require方法,实现代码的模块加载。html

首先,将require.js嵌入网页,而后就能在网页中进行模块化编程了。java

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

上面代码的data-main属性不可省略,用于指定主代码所在的脚本文件,在上例中为scripts子目录下的main.js文件。用户自定义的代码就放在这个main.js文件中。node

define方法:定义模块

define方法用于定义模块,RequireJS要求每一个模块放在一个单独的文件里。jquery

按照是否依赖其余模块,能够分红两种状况讨论。第一种状况是定义独立模块,即所定义的模块不依赖其余模块;第二种状况是定义非独立模块,即所定义的模块依赖于其余模块。git

(1)独立模块github

若是被定义的模块是一个独立模块,不须要依赖任何其余模块,能够直接用define方法生成。web

define({ method1: function() {}, method2: function() {}, });

上面代码生成了一个拥有method一、method2两个方法的模块。ajax

另外一种等价的写法是,把对象写成一个函数,该函数的返回值就是输出的模块。正则表达式

define(function () { return { method1: function() {}, method2: function() {}, }; }); 

后一种写法的自由度更高一点,能够在函数体内写一些模块初始化代码。

值得指出的是,define定义的模块能够返回任何值,不限于对象。

(2)非独立模块

若是被定义的模块须要依赖其余模块,则define方法必须采用下面的格式。

define(['module1', 'module2'], function(m1, m2) { ... });

define方法的第一个参数是一个数组,它的成员是当前模块所依赖的模块。好比,[‘module1’, ‘module2’]表示咱们定义的这个新模块依赖于module1模块和module2模块,只有先加载这两个模块,新模块才能正常运行。通常状况下,module1模块和module2模块指的是,当前目录下的module1.js文件和module2.js文件,等同于写成[’./module1’, ‘./module2’]。

define方法的第二个参数是一个函数,当前面数组的全部成员加载成功后,它将被调用。它的参数与数组的成员一一对应,好比function(m1, m2)就表示,这个函数的第一个参数m1对应module1模块,第二个参数m2对应module2模块。这个函数必须返回一个对象,供其余模块调用。

define(['module1', 'module2'], function(m1, m2) { return { method: function() { m1.methodA(); m2.methodB(); } }; });

上面代码表示新模块返回一个对象,该对象的method方法就是外部调用的接口,menthod方法内部调用了m1模块的methodA方法和m2模块的methodB方法。

须要注意的是,回调函数必须返回一个对象,这个对象就是你定义的模块。

若是依赖的模块不少,参数与模块一一对应的写法很是麻烦。

define( [ 'dep1', 'dep2', 'dep3', 'dep4', 'dep5', 'dep6', 'dep7', 'dep8'], function(dep1, dep2, dep3, dep4, dep5, dep6, dep7, dep8){ ... } );

为了不像上面代码那样繁琐的写法,RequireJS提供一种更简单的写法。

define( function (require) { var dep1 = require('dep1'), dep2 = require('dep2'), dep3 = require('dep3'), dep4 = require('dep4'), dep5 = require('dep5'), dep6 = require('dep6'), dep7 = require('dep7'), dep8 = require('dep8'); ... } });

下面是一个define实际运用的例子。

define(['math', 'graph'], function ( math, graph ) { return { plot: function(x, y){ return graph.drawPie(math.randomGrid(x,y)); } } }; );

上面代码定义的模块依赖math和graph两个库,而后返回一个具备plot接口的对象。

另外一个实际的例子是,经过判断浏览器是否为IE,而选择加载zepto或jQuery。

define(('__proto__' in {} ? ['zepto'] : ['jquery']), function($) { return $; }); 

上面代码定义了一个中间模块,该模块先判断浏览器是否支持__proto__属性(除了IE,其余浏览器都支持),若是返回true,就加载zepto库,不然加载jQuery库。

require方法:调用模块

require方法用于调用模块。它的参数与define方法相似。

require(['foo', 'bar'], function ( foo, bar ) { foo.doSomething(); });

上面方法表示加载foo和bar两个模块,当这两个模块都加载成功后,执行一个回调函数。该回调函数就用来完成具体的任务。

require方法的第一个参数,是一个表示依赖关系的数组。这个数组能够写得很灵活,请看下面的例子。

require( [ window.JSON ? undefined : 'util/json2' ], function ( JSON ) { JSON = JSON || window.JSON; console.log( JSON.parse( '{ "JSON" : "HERE" }' ) ); });

上面代码加载JSON模块时,首先判断浏览器是否原生支持JSON对象。若是是的,则将undefined传入回调函数,不然加载util目录下的json2模块。

require方法也能够用在define方法内部。

define(function (require) { var otherModule = require('otherModule'); });

下面的例子显示了如何动态加载模块。

define(function ( require ) { var isReady = false, foobar; require(['foo', 'bar'], function (foo, bar) { isReady = true; foobar = foo() + bar(); }); return { isReady: isReady, foobar: foobar }; }); 

上面代码所定义的模块,内部加载了foo和bar两个模块,在没有加载完成前,isReady属性值为false,加载完成后就变成了true。所以,能够根据isReady属性的值,决定下一步的动做。

下面的例子是模块的输出结果是一个promise对象。

define(['lib/Deferred'], function( Deferred ){ var defer = new Deferred(); require(['lib/templates/?index.html','lib/data/?stats'], function( template, data ){ defer.resolve({ template: template, data:data }); } ); return defer.promise(); });

上面代码的define方法返回一个promise对象,能够在该对象的then方法,指定下一步的动做。

若是服务器端采用JSONP模式,则能够直接在require中调用,方法是指定JSONP的callback参数为define。

require( [ "http://someapi.com/foo?callback=define" ], function (data) { console.log(data); });

require方法容许添加第三个参数,即错误处理的回调函数。

require( [ "backbone" ], function ( Backbone ) { return Backbone.View.extend({ /* ... */ }); }, function (err) { // ... } );

require方法的第三个参数,即处理错误的回调函数,接受一个error对象做为参数。

require对象还容许指定一个全局性的Error事件的监听函数。全部没有被上面的方法捕获的错误,都会被触发这个监听函数。

requirejs.onError = function (err) { // ... };

AMD模式小结

define和require这两个定义模块、调用模块的方法,合称为AMD模式。它的模块定义的方法很是清晰,不会污染全局环境,可以清楚地显示依赖关系。

AMD模式能够用于浏览器环境,而且容许非同步加载模块,也能够根据须要动态加载模块。

配置require.js:config方法

require方法自己也是一个对象,它带有一个config方法,用来配置require.js运行参数。config方法接受一个对象做为参数。

require.config({ paths: { jquery: [ '//cdnjs.cloudflare.com/ajax/libs/jquery/2.0.0/jquery.min.js', 'lib/jquery' ] } });

config方法的参数对象有如下主要成员:

(1)paths

paths参数指定各个模块的位置。这个位置能够是同一个服务器上的相对位置,也能够是外部网址。能够为每一个模块定义多个位置,若是第一个位置加载失败,则加载第二个位置,上面的示例就表示若是CDN加载失败,则加载服务器上的备用脚本。须要注意的是,指定本地文件路径时,能够省略文件最后的js后缀名。

require(["jquery"], function($) { // ... });

上面代码加载jquery模块,由于jquery的路径已经在paths参数中定义了,因此就会到事先设定的位置下载。

(2)baseUrl

baseUrl参数指定本地模块位置的基准目录,即本地模块的路径是相对于哪一个目录的。该属性一般由require.js加载时的data-main属性指定。

(3)shim

有些库不是AMD兼容的,这时就须要指定shim属性的值。shim能够理解成“垫片”,用来帮助require.js加载非AMD规范的库。

require.config({ paths: { "backbone": "vendor/backbone", "underscore": "vendor/underscore" }, shim: { "backbone": { deps: [ "underscore" ], exports: "Backbone" }, "underscore": { exports: "_" } } });

上面代码中的backbone和underscore就是非AMD规范的库。shim指定它们的依赖关系(backbone依赖于underscore),以及输出符号(backbone为“Backbone”,underscore为“_”)。

插件

RequireJS容许使用插件,加载各类格式的数据。完整的插件清单能够查看官方网站

下面是插入文本数据所使用的text插件的例子。

define([ 'backbone', 'text!templates.html' ], function( Backbone, template ){ // ... });

上面代码加载的第一个模块是backbone,第二个模块则是一个文本,用’text!’表示。该文本做为字符串,存放在回调函数的template变量中。

优化器r.js

RequireJS提供一个基于node.js的命令行工具r.js,用来压缩多个js文件。它的主要做用是将多个模块文件压缩合并成一个脚本文件,以减小网页的HTTP请求数。

第一步是安装r.js(假设已经安装了node.js)。

npm install -g requirejs

而后,使用的时候,直接在命令行键入如下格式的命令。

node r.js -o <arguments>

<argument>表示命令运行时,所须要的一系列参数,好比像下面这样:

node r.js -o baseUrl=. name=main out=main-built.js

除了直接在命令行提供参数设置,也能够将参数写入一个文件,假定文件名为build.js。

({ baseUrl: ".", name: "main", out: "main-built.js" })

而后,在命令行下用r.js运行这个参数文件,就OK了,不须要其余步骤了。

node r.js -o build.js

下面是一个参数文件的范例,假定位置就在根目录下,文件名为build.js。

({ appDir: './', baseUrl: './js', dir: './dist', modules: [ { name: 'main' } ], fileExclusionRegExp: /^(r|build)\.js$/, optimizeCss: 'standard', removeCombined: true, paths: { jquery: 'lib/jquery', underscore: 'lib/underscore', backbone: 'lib/backbone/backbone', backboneLocalstorage: 'lib/backbone/backbone.localStorage', text: 'lib/require/text' }, shim: { underscore: { exports: '_' }, backbone: { deps: [ 'underscore', 'jquery' ], exports: 'Backbone' }, backboneLocalstorage: { deps: ['backbone'], exports: 'Store' } } })

上面代码将多个模块压缩合并成一个main.js。

参数文件的主要成员解释以下:

  • appDir:项目目录,相对于参数文件的位置。

  • baseUrl:js文件的位置。

  • dir:输出目录。

  • modules:一个包含对象的数组,每一个对象就是一个要被优化的模块。

  • fileExclusionRegExp:凡是匹配这个正则表达式的文件名,都不会被拷贝到输出目录。

  • optimizeCss: 自动压缩CSS文件,可取的值包括“none”, “standard”, “standard.keepLines”, “standard.keepComments”, “standard.keepComments.keepLines”。

  • removeCombined:若是为true,合并后的原文件将不保留在输出目录中。

  • paths:各个模块的相对路径,能够省略js后缀名。

  • shim:配置依赖性关系。若是某一个模块不是AMD模式定义的,就能够用shim属性指定模块的依赖性关系和输出值。

  • generateSourceMaps:是否要生成source map文件。

更详细的解释能够参考官方文档

运行优化命令后,能够前往dist目录查看优化后的文件。

下面是另外一个build.js的例子。

({ mainConfigFile : "js/main.js", baseUrl: "js", removeCombined: true, findNestedDependencies: true, dir: "dist", modules: [ { name: "main", exclude: [ "infrastructure" ] }, { name: "infrastructure" } ] })

上面代码将模块文件压缩合并成两个文件,第一个是main.js(指定排除infrastructure.js),第二个则是infrastructure.js。

参考连接

相关文章
相关标签/搜索