前面的话

  由CommonJS组织提出了许多新的JavaScript架构方案和标准,希望能为前端开发提供统一的指引。AMD规范就是其中比较著名一个,全称是Asynchronous Module Definition,即异步模块加载机制,完整描述了模块的定义,依赖关系,引用关系以及加载机制。而AMD规范的作者亲自实现了符合AMD规范的requireJS。本文将详细介绍AMD及requireJS

AMD规范

  AMD(Asynchronous Module Definition)翻译为异步模块定义。异步强调的是,在加载模块以及模块所依赖的其它模块时,都采用异步加载的方式,避免模块加载阻塞了网页的渲染进度

  AMD作为一个规范,只需定义其语法API,而不关心其实现。AMD规范简单到只有一个API,即define函数

define([module-name?], [array-of-dependencies?], [module-factory-or-object]);

  module-name: 模块标识,可以省略

  array-of-dependencies: 所依赖的模块,可以省略

  module-factory-or-object: 模块的实现,或者一个JavaScript对象

  define函数具有异步性。当define函数执行时,首先会异步的去调用第二个参数中列出的依赖模块,当所有的模块被载入完成之后,如果第三个参数是一个回调函数则执行;然后告诉系统模块可用,也就通知了依赖于自己的模块自己已经可用

加载

  使用require.js的第一步,是先去官方网站下载最新版本。下载后,假定把它放在js子目录下面,就可以加载了

<script src="js/require.js"></script>

  HTML中的script标签在加载和执行过程中会阻塞网页的渲染,所以一般要求尽量将script标签放置在body元素的底部,以便加快页面显示的速度,还有一种方式就是通过异步加载的方式来加载js文件,这样可以避免js文件对html渲染的阻塞

<script src="js/require.js" defer async></script>

入口文件

  require.js在加载的时候会检查data-main属性,当requireJS自身加载执行后,就会再次异步加载data-main属性指向的main.js。这个main.js是当前网页所有逻辑的入口,理想情况下,整个网页只需要这一个script标记,利用requireJS加载依赖的其它文件

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

  [注意]在main.js中所设置的脚本是异步加载的。所以如果在页面中配置了其它JS加载,则不能保证它们所依赖的JS已经加载成功

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

【内部机制】

  在RequireJS内部,会使用head.appendChild()将每一个模块依赖加载为一个script标签。RequireJS等待所有的依赖加载完毕,计算出模块定义函数正确调用顺序,然后依次调用它们

模块

  模块不同于传统的脚本文件,它良好地定义了一个作用域来避免全局名称空间污染。它可以显式地列出其依赖关系,并以函数(定义此模块的那个函数)参数的形式将这些依赖进行注入,而无需引用全局变量。RequireJS的模块是模块模式的一个扩展,其好处是无需全局地引用其他模块

  RequireJS的模块语法允许它尽快地加载多个模块,虽然加载的顺序不定,但依赖的顺序最终是正确的。同时因为无需创建全局变量,甚至可以做到在同一个页面上同时加载同一模块的不同版本

  一个文件应该只定义1个模块。多个模块可以使用内置优化工具将其组织打包

  如果我们的代码不依赖任何其他模块,那么可以直接写入javascript代码

//main.js
console.log(1);

  但这样的话,就没必要使用require.js了。真正常见的情况是,主模块依赖于其他模块,这时就要使用AMD规范定义的的require()函数

// main.js
require(['moduleA'], function(a){
  console.log(a);
});
//moduleA.js
define(function(){
return 1;
})

  这里抛出一个问题,为什么主模块使用的是require()函数,而模块moduleA使用define()函数呢?因为define()定义的模块可以被调用,而require()不可以。主模块main.js是入口文件,需要调用别的模块,而不需要被别的模块调用,所以使用require()或define()都可以。而moduleA需要被调用,所以只能使用define()

  如果把moduleA.js中的define()方法改为require()方法,则返回undefined

// main.js
require(['moduleA'], function(a){
  console.log(a);
});
//moduleA.js
require(function(){
return 1;
})

【简单的值对】

  上面的模块moduleA中,回调函数返回了一个数字。而实际上,模块可以有多种形式,比如一个简单的值对

define({
color: "black",
size: "unisize"
});

  返回的结果如下:

【函数式定义】

  如果一个模块没有任何依赖,但需要一个做setup工作的函数,则在define()中定义该函数,并将其传给define()

define(function () {
//Do setup work here
return {
color: "black",
size: "unisize"
}
});

  返回的结果如下:

【存在依赖的函数式定义】

  如果模块存在依赖:则第一个参数是依赖的名称数组;第二个参数是函数,在模块的所有依赖加载完毕后,该函数会被调用来定义该模块,因此该模块应该返回一个定义了本模块的object。依赖关系会以参数的形式注入到该函数上,参数列表与依赖名称列表一一对应

//moduleA.js
define(['moduleB'], function(b) {
var num = 10;
return b.add(num);
}
);
////moduleB.js
define({
add: function(n){
return n+1;
}
});

【命名模块】

  define()中可以包含一个模块名称作为首个参数

//moduleA.js
define("moduleA",['moduleB'], function(b) {
var num = 10;
return b.add(num);
}
);

  这些常由优化工具生成。也可以自己显式指定模块名称,但这使模块更不具备移植性——就是说若将文件移动到其他目录下,就得重命名。一般最好避免对模块硬编码,而是交给优化工具去生成。优化工具需要生成模块名以将多个模块打成一个包,加快到浏览器的载入速度

路径配置

  html中的base元素用于指定文档里所有相对URL地址的基础URL,requireJS的baseUrl跟这个base元素起的作用是类似的,由于requireJS总是动态地请求依赖的JS文件,所以必然涉及到一个JS文件的路径解析问题,requireJS默认采用一种baseUrl + moduleID的解析方式,requireJS对它的处理遵循如下规则:

  1、在没有使用data-main和config的情况下,baseUrl默认为当前页面的目录

  2、在有data-main的情况下,main.js前面的部分就是baseUrl,比如上面的js/

  3、在有config的情况下,baseUrl以config配置的为准

  上述三种方式,优先级由低到高排列

  RequireJS以一个相对于baseUrl的地址来加载所有的代码。页面顶层script标签含有一个特殊的属性data-main,require.js使用它来启动脚本加载过程,而baseUrl一般设置到与该属性相一致的目录

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

  在模块章节的示例中,代码如下所示

// main.js
require(['moduleA'], function(a){
  console.log(a);
});
//moduleA.js
define(function(){
return 1;
})

  入口文件main.js依赖于moduleA,直接写成['moduleA'],默认情况下,require.js假定moduleA与main.js在同一个目录,即'js/moduleA.js',文件名为moduleA.js,然后自动加载

  使用require.config()方法,我们可以对模块的加载行为进行自定义。require.config()就写在主模块(main.js)的头部。参数就是一个对象,这个对象的paths属性指定各个模块的加载路径

  下面在demo文件夹下新建一个test文件夹,并在test文件夹下新建一个moduleA.js文件,内容如下

//moduleA.js
define(function(){
return 2;
})

  而在原来的js文件夹下,依然存在一个moduleA.js文件,内容如下

//moduleA.js
define(function() {
return 1;
});

  当js文件夹下的main.js进行config配置时

// main.js
require.config({
baseUrl: 'test'
})
require(['moduleA'], function(a){
console.log(a);
});

   结果为2,说明识别的是'test/moduleA.js'文件

  当js文件夹下的main.js不进行config配置时

// main.js
require(['moduleA'], function(a){
console.log(a);
});

  结果为1,说明识别的是'js/moduleA.js'文件

  RequireJS默认假定所有的依赖资源都是js脚本,因此无需在module ID上再加".js"后缀,RequireJS在进行module ID到path的解析时会自动补上后缀

  如果一个模块的路径比较深,或者文件名特别长,比如'js/lib/moduleA.min.js',则可以使用config配置对象中的paths属性

// main.js
require.config({
paths:{
'moduleA':'lib/moduleA.min'
}
})
require(['moduleA'], function(a){
console.log(a);
}); //moduleA-min.js
define(function(){
return 3;
})

  结果为3

  要注意的是,这里的paths的'moduleA'设置的是'lib/moduleA.min',而不是'js/lib/moduleA.min',是因为requireJS中的文件解析是一个"baseUrl + paths"的解析过程

  在index.html的入口文件设置的是'js/main',所以baseURL是'js'。因此'baseUrl + paths' = 'js/lib/moduleA.min'

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

  如果在config配置对象中设置了baseUrl,则以此为准

// main.js
require.config({
baseUrl: 'js/lib',
paths:{
'moduleA':'moduleA.min'
}
})
require(['moduleA'], function(a){
console.log(a);
});

  结果同样为3,baseURL是'js/lib',paths是'moduleA.min'。因此'baseUrl + paths' = 'js/lib/moduleA.min'

  如果一个module ID符合下述规则之一,其ID解析会避开常规的"baseUrl + paths"配置,而是直接将其加载为一个相对于当前HTML文档的脚本:1、以 ".js" 结束;2、包含 URL 协议,如 "http:" or "https:"

  如下所示,require()函数所依赖的模块路径为'js/moduleA.js'

// main.js
require.config({
baseUrl: 'js/lib',
paths:{
'moduleA':'moduleA.min'
}
})
require(['js/moduleA.js'], function(a){
console.log(a);
});

  而该文件的代码如下,路径为'js/moduleA.js',而不是'js/lib/moduleA.min',所以,最终结果为1

//moduleA.js
define(function() {
return 1;
});

  一般来说,最好还是使用baseUrl及"paths" config去设置module ID。它会带来额外的灵活性,如便于脚本的重命名、重定位等。 同时,为了避免凌乱的配置,最好不要使用多级嵌套的目录层次来组织代码,而是要么将所有的脚本都放置到baseUrl中,要么分置为项目库/第三方库的一个扁平结构,如下

www/
index.html
js/
app/
sub.js
lib/
jquery.js
canvas.js
main.js

CommonJS

  前面提到过,commonJS主要应用于服务器端编程,如nodejs。使用打包工具Browserify可以对CommonJS进行格式转换,使其可以在浏览器端进行

  而requireJS支持一种简单包装CommonJS的方式,只要在commonJS代码的外层简单包裹一层函数,就可以在浏览器端直接运行

define(function(require, exports, module) {

});

  如果该模块还依赖其他模块,如依赖模块moduleA,则代码如下

define(['moduleA'],function(require, exports, module) {

});

  a.js和b.js的commonJS形式的代码如下

// a.js
var a = 100;
module.exports.a = a; // b.js
var result = require('./a');
console.log(result.a);

  index.html直接引用b.js会报错,提示require没有被定义

<script src="b.js"></script> 

  将a.js和b.js进行改造之后,代码如下

// a.js
define(function(require, exports, module) {
var a = 100;
module.exports.a = a;
}); // b.js
define(function(require, exports, module) {
var result = require('./a');
console.log(result.a);
});

  index.html将入口文件设置为'js/b',则结果为100

<script src="require.js" data-main="js/b" defer async></script>

懒加载

  有如下例子,入口文件main.js代码如下

// main.js
require(['a'], function(a){
console.log('main');
document.onclick = function(){
a.test();
}
});

  所依赖的模块a.js的代码如下

define(function(){
console.log('a');
return {
test : function(){
console.log('a.test');
}
}
})

  在浏览器端执行时,即使不点击页面,浏览器也会下载a.js文件。这个性能消耗是不容忽视的

  AMD保留了commonjs中的require、exprots、module这三个功能。可以不把依赖罗列在dependencies数组中。而是在代码中用require来引入

  重写后的代码如下

// main.js
define(function(){
console.log('main');
document.onclick = function(){
require(['a'],function(a){
a.test();
});
}
});
//a.js
define(function(){
  console.log('a');
return {
test : function(){
console.log('a.test');
}
}
})

  在浏览器端执行时,如果不点击页面,浏览器就不会下载a.js文件,这样就实现懒加载

其他配置

  在requireJS中,除了路径配置之外,还有一些其他配置

【配置设置】

  在前面的例子中,我们配置requireJS中的路径是通过入口文件main.js中的config对象来配置的。实际上,不通过入口文件,也可以进行requireJS的配置

  1、在index.html文件嵌入javascript代码

  在HTML文件中,加载requireJS文件之后,立即对requireJS进行配置,相当于将main.js文件变为内嵌的javascript文件

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<script src="require.js"></script>
<script>
require.config({
baseUrl: 'js/lib',
paths:{
'moduleA':'moduleA.min'
}
})
require(['moduleA'], function(a){
console.log(a);
});
</script>
</body>
</html>

  2、将配置作为全局变量"require"在require.js加载之前进行定义,它会被自动应用

  这里有一个问题是,如果require作为全局变量被提前定义,则data-main入口文件,是以baseUrl为基础进行设置的

  [注意]使用 var require = {} 的形式而不是 window.require = {}的形式。后者在IE中运行不正常

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<script>
var require = {
baseUrl: 'js/lib',
paths:{
'moduleA':'moduleA.min'
}
}
</script>
<script src="require.js" data-main="../main"></script>
</body>
</html>

【shim】

  shim属性为那些没有使用define()来声明依赖关系、设置模块的"浏览器全局变量注入"型脚本做依赖和导出配置,即加载非规范的模块

  举例来说,underscore和backbone这两个库,都没有采用AMD规范编写。如果要加载它们的话,必须先定义它们的特征。具体来说,每个模块要定义(1)exports值(输出的变量名),表明这个模块外部调用时的名称;(2)deps数组,表明该模块的依赖性

  通过如下配置后,现在可以通过_调用underscore的api,使用Backbone来调用backbone的api

  require.config({
    shim: {       'underscore':{
        exports: '_'
      },
      'backbone': {
        deps: ['underscore', 'jquery'],
        exports: 'Backbone'
      }
    }
  });

  jQuery的插件可以如下这样定义,现在可以通过jQuery.fn.scroll来调用该插件的api

  shim: {
    'jquery.scroll': {
      deps: ['jquery'],
      exports: 'jQuery.fn.scroll'
    }
  }

插件

  require.js还提供一系列插件,实现一些特定的功能

【dom ready】 

  RequireJS加载模块速度很快,很有可能在页面DOM Ready之前脚本已经加载完毕。需要与DOM交互的工作应等待DOM Ready。现代的浏览器通过DOMContentLoaded事件来知会

  但是,不是所有的浏览器都支持DOMContentLoaded。domReady模块实现了一个跨浏览器的方法来判定何时DOM已经ready

// main.js
require(['domready!'], function(){
console.log('ready');
});

【text】

  text插件可以用来加载如.html、.css等文本文件,可以通过该插件来实现完整组件(结构+逻辑+样式)的组件化开发

require(["some/module", "text!some/module.html", "text!some/module.css"],
function(module, html, css) {
}
);

AMD及requireJS的更多相关文章

  1. CommonJS, AMD 和 RequireJS之间的关系(转载)

    先说说CommonJS CommonJS - 大家是不是觉得JavaScript仅仅是一个客户端的编译语言,其实JavaScript设计之初不仅仅是针对客户端设计的语言.后来只是由于Web的迅速流行, ...

  2. 【JavaScript】JavaScript模块化编程 - CommonJS, AMD 和 RequireJS之间的关系

    通行的Javascript模块规范共有两种:CommonJS和AMD 先说说CommonJS   CommonJS - 大家是不是觉得JavaScript仅仅是一个客户端的编译语言,其实JavaScr ...

  3. JavaScript模块化编程 - CommonJS, AMD 和 RequireJS之间的关系

    这几天在学习CommonJS的时候突然在StackOverflow上搜索到一个非常好的一个帖子,是关于CommonJS, AMD和RequireJS之间的关系的问答贴.我感觉写的非常好,鉴于没有找到相 ...

  4. Javascript Module pattern template. Shows a class with a constructor and public/private methods/properties. Also shows compatibility with CommonJS(eg Node.JS) and AMD (eg requireJS) as well as in a br

    /** * Created with JetBrains PhpStorm. * User: scotty * Date: 28/08/2013 * Time: 19:39 */ ;(function ...

  5. AMD和RequireJS初识----优化Web应用前端(按需动态加载JS)

    RequireJS是一个非常小巧的JavaScript模块载入框架,是AMD规范最好的实现者之一.最新版本的RequireJS压缩后只有14K,堪称非常轻量.它还同时可以和其他的框架协同工作,使用Re ...

  6. JS模块化规范AMD之RequireJS

    1.基本操作 加载 JavaScript 文件(入口文件) RequireJS以一个相对于baseUrl的地址来加载所有的代码 <script data-main="scripts/m ...

  7. 深入理解AMD和RequireJS!

    AMD 基于commonJS规范的nodeJS出来以后,服务端的模块概念已经形成,很自然地,大家就想要客户端模块.而且最好两者能够兼容,一个模块不用修改,在服务器和浏览器都可以运行.但是,由于一个重大 ...

  8. AMD规范(RequireJS)、CMD规范(SeaJS)、CommonJS(BravoJS)规范的辨析

    首先,AMD,CMD,CommonJS都实现了文件模块化. 对于依赖的模块:AMD是提前执行:CMD是延迟执行: AMD是依赖前置,CMD是依赖就近: AMD官方解释:https://github.c ...

  9. 详解AMD规范及具体实现requireJS在工程中的使用

    前面的话 由CommonJS组织提出了许多新的JavaScript架构方案和标准,希望能为前端开发提供统一的指引.AMD规范就是其中比较著名一个,全称是Asynchronous Module Defi ...

随机推荐

  1. GNU/Linux与开源文化的那些人和事

     一.计算机的发明 世上本无路,走的人多了,就有了路.世上本无计算机,琢磨的人多了--没有计算机,一切无从谈起. 三个人对计算机的发明功不可没,居功至伟.阿兰·图灵(Alan Mathison Tur ...

  2. web service 组件

    web service 组件 基本的 web service 平台是 XML + HTTP.所有标准的 web service 使用以下组件: SOAP(简单对象访问协议) UDDI(通用描述.发现与 ...

  3. .Net EF框架的增删改查

    创建上下文对象: WordBoradEntities db = new WordBoradEntities(); 添加: //1.1创建实体对象 User uObj = new User() { uN ...

  4. Android Weekly Notes Issue #251

    Android Weekly Issue #251 April 2nd, 2017 Android Weekly Issue #251. 本期内容: Android O新增的API: View的too ...

  5. ios坐标位置转换

    //ios常用坐标转换来处理一些下拉框队形的按钮的位置,我以最下面两个来进行一下个人的理解,不足之处多多见谅 - (CGPoint)convertPoint:(CGPoint)point toView ...

  6. 1136: 零起点学算法43——多组测试数据输出I

    1136: 零起点学算法43--多组测试数据输出I Time Limit: 1 Sec  Memory Limit: 128 MB   64bit IO Format: %lldSubmitted: ...

  7. linux sort命令详解(转)

    sort命令是帮我们依据不同的数据类型进行排序,其语法及常用参数格式: sort [-bcfMnrtk][源文件][-o 输出文件] 补充说明:sort可针对文本文件的内容,以行为单位来排序. 参 数 ...

  8. html+css底部自动固定底部

    前端在切图过程中,肯定遇见过这种情况. 页面结构由三个部分组成,头部.内容.底部. 当一个页面的内容没撑满屏幕时,底部是跟着内容而并列存在的. 这个时候如果是大屏的话,底部下面会有多余的空白区域,而网 ...

  9. FineUIMvc

    FineUIMvc 在线示例源代码下载(包含上面列出的全部 50 种主题,基础版可以免费使用) FineUIMvc 在线示例 FineUI 官网首页

  10. redis object 对象系统

    redis object对象系统 概述 redis 当中, sds字符串, adlist双向链表, dict字典, ziplist压缩链表, intset整数集合等均为底层数据结构 redis 并没有 ...