AMD/RequireJS 使用入门

参考资料

RequireJS 中文网
Javascript模块化编程(三):require.js的用法——阮一峰

前言

本人菜鸟,入IT只为当鼓励师。本编文章意在总结 RequireJS 的目的和用法。

一、require.js 的 目的

1.1 管理模块间的依赖性,便于代码的编写和维护

RequireJS 鼓励代码的模块化,主要目的是为了代码的模块化。

  • 如果一个文件需要依赖另外一些文件中定义的东西时,这个文件依赖的所有文件都要在它之前导入。过于复杂的系统,依赖关系可能出现相互交叉的情况,依赖关系的管理就更加难了。
    例如:51行到60行,分别是编写的10个模块。第61行引入的 main.js 是主模块,编写的是程序运行的过程。main.js 这个文件用到了从51行到60行的模块,而它并没有被其他模块使用,故可以且必须放在最末尾导入。而上面的10个模块,要确保模块的依赖在该模块导入之前就要导入,为了解除导入顺序的限制,只能让各模块间解耦。


  • 而 RequireJS 使用了不同于传统 <script>标签 的脚本加载步骤。后续我们会看到 其引入js文件的方式是怎样的。

1.2 实现js文件的异步加载,避免网页失去响应

使用了 <script>标签 这种传统的引入js文件的方式,在加载js文件的时候,浏览器会停止网页渲染,加载文件越多,网页失去响应的时间就会越长。

若读者对js模块化还不甚理解,可先阅读本小姐写的另一篇文章 浅谈JavaScript 模块化

二、下载RequireJS

2.1 链接下载

require.js 2.1.11
require.js 2.1.11 压缩版
r.js 2.1.11
r.js 可以让你进行优化并能够在 Node, Rhino 或者 xpcshell 中运行。

2.2 npm下载

若当前目录下没有找到 package.json 文件,则输入 package init ,输入完后设置好一些参数,即可生成 package.json 文件。


输入 npm install requirejs --save-dev,下载完成后,可以在
package.json 文件中找到如下这行依赖。


在当前目录下找到 node_modules\requirejs 路径,即可找到 require.js 文件,找到 node_modules\requirejs\bin 路径,即可找到 r.js 文件。



三、require.js 的加载

下载完 require.js 文件后,为了方便,我把文件复制到了 ./scripts/libs 目录中(. 表示当前目录,相对于 index.html 文件)。

RequireJS以一个相对于 baseUrl 的地址来加载所有的代码。如果没有显式指定 configdata-main,则默认的 baseUrl 为包含 RequireJS 的那个 HTML 页面的所属目录。

3.1 引入 require.js 文件

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

加载这个文件,因为浏览器是同步加载的,也可能会造成网页失去响应。
那么,你可以把它放在网页底部加载:


或者,你可以将它写成:

<script src="scripts/libs/require.js" defer asyn="true"></script>

async属性表示该文件需异步加载,避免网页失去响应。但IE浏览器不支持async,只支持defer,所以把defer也写上。

3.2 引入网页程序的主模块

data-main 入口点

页面顶层 <script> 标签含有一个特殊的属性 data-main,require.js使用它来启动脚本加载过程,即指定网页程序的主模块。主模块文件的命名一般可为 main.js。我们把 script 目录下的 main.js 引入:


RequireJS默认假定所有的依赖资源都是js脚本,故可把main.js简写成main

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

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

// main.js
require.config({
    paths: {
        foo: 'libs/foo-1.1.3'
    }
});

// other.js
/**
* 由于 main.js 中的 foo 模块是异步加载的,
* other.js 可能在 foo 模块加载完之前就已经加载执行,
* 此时 other.js 中的 foo 模块 指的是 scripts/foo.js 而非 libs/foo-1.1.3。
*/
require( ['foo'], function( foo ) {

});

四、主模块的写法

main.js "主模块",是整个网页的入口代码。它有点像 C语言 的 main() 函数,所有代码都从这里开始运行。

  • main.js 若不依赖其他模块,则可以直接写js代码:
    // main.js
    console.log("加载成功!");
    这就相当于 C语言 中,直接把代码写在 main() 函数中:
    void main(int arg[], char* agvs[]) {
    printf("加载成功!");
    }
  • 但正常情况下,主模块会依赖于其他模块,这时就要使用AMD规范定义的 require() 函数。
    // main.js
    require(['moduleA', 'moduleB', 'moduleC'], function (moduleA, moduleB, moduleC){
    // 运行代码
    });
    /**
    * dependency_array:依赖数组
    * callback_func:回调函数
    * require(dependency_array, callback_func);
    /
    require() 函数接收两个参数:
    第一个参数是一个数组,表示所依赖的模块有哪些;
    第二个参数是一个回调函数*,当前面指定的模块都加载成功后,它将被调用。加载的模块会以参数形式传入该函数,从而在回调函数内部就可以使用这些模块。
    require() 异步加载 moduleA、moduleB 和 moduleC,浏览器不会失去响应;它指定的回调函数,只有前面的模块都加载成功后,才会运行,解决了依赖性的问题。

五、模块的加载

使用 require.config() 方法,我们可以对模块的加载行为进行自定义。require.config() 方法接收一个参数,该参数为包含一些指定属性的原生对象,我们可通过设置对应属性的值来修改加载行为。这些属性有:baseUrlpathshimmapconfig 等等。

5.1 设置 path(module ID)

RequireJS 鼓励在使用脚本时 以 module ID 替代 URL 地址,默认假定所有的依赖资源都是 js 脚本,因此无需在 module ID上再加 ".js" 后缀。
我们可以设置 require.config() 方法 中 传入对象 的 path 属性,来设置各模块的 module ID。

requirejs.config({
    paths: {
        Bird: 'scripts/views/Bird',
        Block: 'scripts/views/Block',
        Counter: 'scripts/views/Counter',
        GameBg: 'scripts/views/GameBg',
        GameOver: 'scripts/views/GameOver',
        GrassLand: 'scripts/views/GrassLand',
        StartBtn: 'scripts/views/StartBtn',
        StartInfo: 'scripts/views/StartInfo',
        RandomCreator: 'scripts/utils/RandomCreator'
    }
});

// 加载 scripts/views/Bird.js 和 scripts/utils/randomCreator.js
require(['Bird', 'randomCreator'], function (Bird, randomCreator) {
    // 代码
});

5.2 设置 baseUrl

baseUrl 可通过 requirejs.config() 手动设置。如果没有显式指定 config
data-main,则默认的 baseUrl 为包含 RequireJS 的那个 HTML 页面的所属目录。设置以后,加载模块时,路径都会被解析为 baseUrl + path。若想避开该解析过程,设置 path 时可以:

  • 以 ".js" 结束;
  • 以 "/" 开始;
  • 包含 URL 协议, 如 "http:" or "https:"。
requirejs.config({
    baseUrl: "scripts/views",
    paths: {
        Bird: 'scripts/views/Bird',
        Block: 'scripts/views/Block',
        Counter: 'scripts/views/Counter',
        GameBg: 'scripts/views/GameBg',
        GameOver: 'scripts/views/GameOver',
        GrassLand: 'scripts/views/GrassLand',
        StartBtn: 'scripts/views/StartBtn',
        StartInfo: 'scripts/views/StartInfo',
        RandomCreator: '../utils/RandomCreator'
    }
});

// 加载 scripts/views/Bird.js 和 scripts/utils/randomCreator.js
require(['Bird', 'RandomCreator'], function (Bird, randomCreator) {
    // 代码
});

如果某个模块在另一台主机上,也可直接指定其网址:

require.config({
    paths: {
        "jquery": "https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min"
    }
});

require.js 要求,每个模块是一个单独的 js 文件。如果加载多个模块,就会发出多次HTTP请求,影响网页的加载速度。因此,require.js 提供了一个 优化工具,当模块部署完毕后,可用该工具将多个模块合并成一个文件,减少HTTP请求数。

5.3 设置 shim

理论上,require.js 加载的模块,必须是按照AMD规范、用 define() 函数定义的模块。但是实际上,虽然已经有一部分流行的函数库(比如jQuery)符合AMD规范,更多的库并不符合。
为了能够加载非规范的模块,可设置 require.config() 方法 传入对象 中的 shim 属性。shim属性的值是一个对象,这个对象里包含一些模板对象,而这些模板对象有两个属性:
① exports值(输出的变量名):表明这个模块外部调用时的名称;
② deps数组:表明该模块的依赖什么模块。

require.config({
    shim: {
        // underscore 模块,外部调用时使用 _ 指代该模块
        'underscore': {
            exports: '_'
        },
        // backbone模块,外部调用时使用 Backbone 指代该模块
        // 这个模块依赖于 underscore , jquery 模块
        'backbone': {
            deps: ['underscore', 'jquery'],
            exports: 'Backbone'
        }
    }
});

5.4 设置 map

对于给定的模块前缀,使用一个不同的模块ID来加载该模块。

requirejs.config({
    map: {
       'some/newmodule': {
            'foo': 'foo1.2'
        },
        'some/oldmodule': {
            'foo': 'foo1.0'
        }
    }
});


另外在map中支持 *,意思是 "对于所有的模块加载,使用本map配置"。如果还有更细化的map配置,会优先于 * 配置。

requirejs.config({
    map: {
        '*': {
            'foo': 'foo1.2'
        },
        'some/oldmodule': {
            'foo': 'foo1.0'
        }
    }
});

意思是: 除了 some/oldmodule 外的所有模块,当要用 foo 时,使用 foo1.2 来替代。对于 some/oldmodule 自己,则使用 foo1.0

5.5 设置 config

若想设置一些配置信息(变量,方法)供对应模块使用,可以设置 require.config() 方法 传入对象 中的 config 属性。要获取这些信息的模块可以加载特殊的依赖 module,并调用 module.config()。如:

// main.js
requirejs.config({
    config: {
        'bar': {
            size: 'large'
        },
        'baz': {
            color: 'blue'
        }
    }
});

// bar.js
define( function (require, exports, module) {
    var size = module.config().size;
    console.log(size);  // large
});

// baz.js
define(['module'], function (module) {
    var color = module.config().color;
    console.log(color);  // blue
});

六、定义模块

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

RequireJS 定义模块采用AMD规范,使用 define() 方法。
假定现在有一个GameBg.js文件,它定义了一个 GameBg 模块:

// GameBg.js
define( function () {
  
    var GameBg = {};

    return GameBg;
});

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

6.1 简单的值对

如果一个模块仅含值对,没有任何依赖,可在 define() 中直接定义这些值对:

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

6.2 函数式定义

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

// GameBg.js
define( function () {
    
    // 一些初始化或配置工作
    // ...

    // 界面背景单例对象
    var GameBg = (function () {
        var _element = document.querySelector('#game-bg'),
            _width = document.querySelector('#game-bg').offsetWidth,
            _height = document.querySelector('#game-bg').offsetHeight;

        return {
            getElement : function () {
                return _element;
            },
            getWidth : function () {
                return _width;
            },
            getHeight : function () {
                return _height;
            },
            // ...
        }
    })();
    return GameBg;
});

6.3 存在依赖的函数式定义

模块函数以参数 GameBgGrassLand 使用这两个以 ./scripts/views/GameBg./scripts/views/GrassLand 名称指定的模块。在这两个模块加载完毕之前,模块函数不会被调用。RequireJS 不鼓励模块定义全局变量,返回的 object 定义了 Bird 模块。这种定义模式下,Bird 不作为一个全局变量而存在。

// Bird.js
define(['GameBg', 'GrassLand'], function (GameBg, GrassLand) {

    // Bird对象的构造函数
    var Bird = function (idName) {

       // ...
    };
    return Bird;
});

6.4 将模块定义为一个函数

模块的返回值类型 不一定是 一个对象,也可以是 一个函数。此处是一个返回了函数的模块定义:

// Bird.js
define(['GameBg', 'GrassLand'], function (GameBg, GrassLand) {

    // Bird对象的构造函数
    return function (idName) {

       // ...
    };
});

6.5 简单包装CommonJS来定义模块

define(function(require, exports, module) {
    var a = require('a'),
        b = require('b');

    // 返回模块
    return function () {};
});

6.6 定义一个命名模块

你可能会看到一些define()中包含了一个模块名称作为首个参数。

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

define("FlappyBird",
    ["GameBg", "GrassLand"],
    function(GameBg, GrassLand) {
        //Define foo/title object in here.
   }
);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容