返回 导航

其他

hangge.com

RequireJS - 入门指南、进阶使用详解(附样例)

作者:hangge | 2017-07-24 08:10
现在项目大都使用模块化开发,而 RequireJS 作为 AMD 模块开发的典范,还是很值得学习使用的。

一、AMD 规范

1,AMD 基本介绍

  • AMD 全称为 Asynchromous Module Definition(异步模块定义) 
  • AMD RequireJS 在推广过程中对模块定义的规范化产出,它是一个在浏览器端模块化开发的规范。
  • AMD 模式可以用于浏览器环境并且允许非同步加载模块,同时又能保证正确的顺序,也可以按需动态加载模块。

2,AMD 模块规范

define(id?, dependencies?, factory)
  • id:指定义中模块的名字(可选)。如果没有提供该参数,模块的名字应该默认为模块加载器请求的指定脚本的名字。如果提供了该参数,模块名必须是“顶级”的和绝对的(不允许相对名字)。
  • dependencies:当前模块依赖的,已被模块定义的模块标识的数组字面量(可选)。
  • factory:一个需要进行实例化的函数或者一个对象。
define(function (require, exports, module) { 
    var reqModule = require("./someModule"); 
    requModule.test(); 
     
    exports.asplode = function () { 
        //someing 
    } 
}); 


二、RequireJS 介绍 

1,什么是 RequireJS

RequireJS 是一个 JavaScript 模块加载器。它非常适合在浏览器中使用,但它也可以用在其他脚本环境, 比如 RhinoNode。使用 RequireJS 加载模块化脚本将提高代码的加载速度和质量。

2,使用 RequireJS 的好处

  • 异步“加载”。使用 RequireJS,会在相关的 js 加载后执行回调函数,这个过程是异步的,所以它不会阻塞页面。
  • 按需加载。通过 RequireJS,你可以在需要加载 js 逻辑的时候再加载对应的 js 模块,不需要的模块就不加载,这样避免了在初始化网页的时候发生大量的请求和数据传输。
  • 更加方便的模块依赖管理。通过 RequireJS 的机制,你能确保在所有的依赖模块都加载以后再执行相关的文件,所以可以起到依赖管理的作用。
  • 更加高效的版本管理。比如原来我们使用的 script 脚本引入的方式来引入一个 jQuery2.x 的文件,但如果有 100 个页面都是这么引用的,如果想换成 jQuery3.x,那你就不得不去改这 100 个页面。而使用 requireJS 只需要改一处地方,即修改 config 中 jQuery path 映射即可。
  • 当然还有一些诸如 cdn 加载不到 js 文件,可以请求本地文件等其它的优点,这里就不一一列举了。

三、RequireJS 的配置和使用

1,下载最新版的 require.js

下载地址:http://requirejs.org/docs/download.html

2,创建一个如下目录结构

(1)lib 文件夹下放置一些需要用到的 js 库,这里除了 require.js 外,还有 jquery
(2)script 文件夹下放置 RequireJS 的入口 js、以及模块 js 文件。
(3)index.html 则为主页面。

3,效果图

(1)页面初始化的时候显示一个按钮。
(2)点击按钮会调用 hello 模块的方法,将信息显示在页面上。
         

4,代码说明

(1)index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>hangge.com</title>
    <script data-main="js/script/main.js" src="js/lib/require.js"></script>
  </head>
  <body>
    <div id="messageBox"></div>
    <button id="btn" type="button" name="button">点击</button>
  </body>
</html>
注意 script 标签,除了指定 require.js 路径外,还有个 data-main 属性:
这属性指定在加载完 reuqire.js 后,就用 requireJS 加载该属性值指定路径下的 JS 文件并运行,所以一般该 JS 文件称为主 JS 文件(其 .js 后缀可以省略)。

(2)main.js
require.config({
    baseUrl: 'js',
    paths: {
        jquery: 'lib/jquery-1.11.1',
    }
});

require(['jquery', 'script/hello'],function ($, hello) {
    $("#btn").click(function(){
      hello.showMessage("hangge.com");
    });
});


要改变 RequireJS 的默认配置,可以使用 require.configh 函数传入一个可选参数对象。下面是一些可以使用的配置:
  • baseUrl:用于加载模块的根路径。在配置这个属性后,以后的文件都是在这个路径下查找内容了。
  • paths:用于一些常用库或者文件夹路径映射,方便后面使用,省得每次都要输入一长串路径。(js 文件的后缀可以省略)
  • shim:加载非 AMD 规范的 js,并解决其载入顺序。
require方法:
require 函数用于加载模块依赖,这里我们加载了 jQuery 以及 hello 这个自定义模块。在加载完毕的回调中,给按钮添加个点击事件,同时点击后会调用 hello 模块中的 showMessage 方法。

(3)hello.js
define(['jquery'],function($){
    //变量定义区
    var moduleName = "hello module";
    var moduleVersion = "1.0";

    //函数定义区
    var showMessage = function(name){
        if(undefined === name){
            return;
        }else{
            $('#messageBox').html('欢迎访问 ' + name);
        }
    };

    //暴露(返回)本模块API
    return {
        "moduleName":moduleName,
        "version": moduleVersion,
        "showMessage": showMessage
    }
});
我们通过 define 方法定义一个 js 模块,并通过 return 对外暴露出接口(两个属性,一个方法)。同时该模块也是依赖于 jQuery

四、require.configh 函数配置说明

要改变 RequireJS 的默认配置,可以使用 require.configh 函数传入一个可选参数对象。上面只演示了其中 baseUrl paths 这两个配置,下面是一个完整的配置:
require.config({
    baseUrl: 'js',
    paths: {
        jquery: 'lib/jquery-1.11.1'
    },
    shim: {
        'backbone': {
            deps: ['underscore', 'jquery'],
            exports: 'Backbone'
        },
        'underscore': {
            exports: '_'
        },
        'modal':{//模态框插件不是模块化
            deps:['jquery'],
            export:"modal"
        },
    },
    map: {
        'script/newmodule': {
            'foo': 'foo1.2'
        },
        'script/oldmodule': {
            'foo': 'foo1.0'
        }
    },
    config: {
        'script/bar': {
            size: 'large'
        },
        'script/baz': {
            color: 'blue'
        }
    }
});

1,baseUrl

用于加载模块的根路径。在配置这个属性后,以后的文件都是在这个路径下查找内容了。

2,paths

用于一些常用库或者文件夹路径映射,方便后面使用,省得每次都要输入一长串路径。(js 文件的后缀可以省略)

3,shim

虽然目前已经有一部分流行的函数库(比如 jQuery)符合 AMD 规范,但还有很多库并不符合。shim 就是为了加载这些非 AMD 规范的 js,并解决其载入顺序的。
比如上面样例,我们想通过 RequireJS 来使用 backbone,那么你就需要在配置中把它定义为一个 shim。同时通过 deps 配置其依赖关系,可以保证 underscorejquery 先被加载。
shim配置的注意事项:
  • shim 配置仅设置了代码的依赖关系,想要实际加载 shim 指定的或涉及的模块,仍然需要一个常规的 require/define 调用。设置 shim 本身不会触发代码的加载。
  • 请仅使用其他"shim"模块作为 shim 脚本的依赖,或那些没有依赖关系,并且在调用 define() 之前定义了全局变量(如 jQuery lodash )的 AMD 库。否则,如果你使用了一个 AMD 模块作为一个 shim 配置模块的依赖,在 build 之后,AMD 模块可能在 shim 托管代码执行之前都不会被执行,这会导致错误。终极的解决方案是将所有 shim 托管代码都升级为含有可选的 AMD define() 调用。

4,map 

(1)对于给定的模块前缀,使用一个不同的模块 ID 来加载该模块。该手段对于某些大型项目很重要。
比如上面配置以后,不同的模块会使用不同版本的"foo":
  • some/newmodule 调用了 require('foo'),它将获取到 foo1.2.js 文件。
  • some/oldmodule 调用 require('foo'),时它将获取到 foo1.0.js 文件。

(2)map 还支持“*”,意思是“对于所有的模块加载,使用本 map 配置”。如果还有更细化的 map 配置,会优先于“*”配置。
比如下面配置,除了“some/oldmodule”外的所有模块,当要用“foo”时,都使用“foo1.2”来替代。
requirejs.config({
    map: {
        '*': {
            'foo': 'foo1.2'
        },
        'some/oldmodule': {
            'foo': 'foo1.0'
        }
    }
});

5,config

我们需要将配置信息传给一个模块。这些配置往往是 application 级别的信息,需要一个手段将它们向下传递给模块。这个通过 requirejs.config() config 配置项就可以实现。

(1)可以通过加载特殊的依赖“module”来获取这些信息。
// script/info.js
define(['module'], function (module) {
    var color = module.config().color;  //blue

});


(2)也可通过符合 CommonJS 规范的模块获取
// script/info.js
define(function (require, exports, module) {
    var color = module.config().color;  //blue

});


五、不同类型的模块定义

  • 模块不同于传统的脚本文件,它良好地定义了一个作用域来避免全局名称空间污染。
  • 它可以显式地列出其依赖关系,并以函数(定义此模块的那个函数)参数的形式将这些依赖进行注入,而无需引用全局变量。同时因为无需创建全局变量,甚至可以做到在同一个页面上同时加载同一模块的不同版本。
  • RequireJS 的模块语法允许它尽快地加载多个模块,虽然加载的顺序不定,但依赖的顺序最终是正确的。
  • 一个磁盘文件应该只定义 1 个模块。多个模块可以使用内置优化工具将其组织打包。

1,简单的键值对模块

(1)如果一个模块仅含键值对,没有任何依赖,则在 define() 中定义这些值对就好了
// script/shirt.js
define({
    color: "black",
    size: "unisize"
});

下面调用并测试这个模块
require(['script/shirt'],function (shirt) {
    console.log("颜色:" + shirt.color);
});


(2)下面还是一个简单的键值对模块,没有任何依赖,但需要做一些初始化 setup 工作。
// script/shirt.js
define(function () {
    //在这里做一些setup工作
    console.log("Do setup work here...");

    return {
        color: "black",
        size: "unisize"
    }
});

2,只有一个主函数的模块

(1)没有任何依赖
// script/info.js
define(function () {
    return function (){
      alert("欢迎访问 hangge.com");
    };
});

下面调用并测试这个模块:
require(['script/info'],function (info) {
    info();
});

(2)存在相关依赖
// script/info.js
define(['jquery'],function($){
    return function (){
      $('#messageBox').html('欢迎访问 hangge.com');
    };
});

下面调用并测试这个模块:
require(['script/info'],function (info) {
    info();
});

3,包含多个函数方法和变量的模块

// script/hello.js
define(['jquery'],function($){
    //变量定义区
    var moduleName = "hello module";
    var moduleVersion = "1.0";

    //函数定义区
    var showMessage = function(name){
        if(undefined === name){
            return;
        }else{
            $('#messageBox').html('欢迎访问 ' + name);
        }
    };

    //暴露(返回)本模块API
    return {
        "moduleName":moduleName,
        "version": moduleVersion,
        "showMessage": showMessage
    }
});

下面调用并测试这个模块:
require(['jquery', 'script/hello'],function ($, hello) {
    $("#btn").click(function(){
      hello.showMessage("hangge.com");
    });
});

六 、相对路径的规则

不管是在配置中写路径还是直接在 require 函数中写路径,我们需要了解 requireJS 在不同情况下的相对路径。以下是相对路径的规则:
  • 如果 <script/> 标签引入 require.js 时没有指定 data-main 属性,则以引入该 js html 文件所在的路径为根路径。
  • 如果有指定 data-main 属性,也就是有指定入口文件,则以入口文件所在的路径为根路径。
  • 如果在 require.config() 中有配置 baseUrl,则以 baseUrl 的路径为根路径。
以上三条优先级逐级提升,如果有重叠,后面的根路径覆盖前面的根路径。

七、循环依赖问题解决

1,什么是循环依赖

(1)假设我们有如下 ab 两个互相依赖的模块(a 依赖 bb 同时依赖 a
--- a.js ---
// script/a.js
define(['script/b'],function(b){
    //函数定义区
    var showMessage = function(){
        console.log("欢迎访问 hangge.com" + b.getName());
    };

    //暴露(返回)本模块API
    return {
        "showMessage": showMessage
    }
});

--- b.js ---
// script/a.js
define(['script/a'],function(a){
    //函数定义区
    var doSomething = function(){
        a.showMessage();
    };

    var getName = function(){
        return "hangge.com";
    };

    //暴露(返回)本模块API
    return {
        "doSomething": doSomething,
        "getName": getName
    }
});

(2)我们如果调用 b 模块的 doSomething 方法
require(['script/b'],function (b) {
    b.doSomething();
});

发现 b 调用 a 正常,但是 a 中调用 b 方法会报 undefined 错误。

2,问题解决

循环依赖比较罕见,它也是一个重构代码重新设计的警示灯。但不管怎样,有时候还是要用到循环依赖。对于循环依赖,只要依赖环中任何一条边是运行时依赖,这个环理论上就是活的。而如果全部边都是装载时依赖,这个环就是死的。
为避免循环依赖引发的问题,其实只要把一边改成运行时依赖就可以了,我们有如下几种方法。

方法1:使用 require() 去获取一个模块
我们对模块 a 进行如下修改,即不再依赖前置加载。而是通过引入 require 依赖,然后再通过 require() 方法去载入模块 b,并在回调中去执行。
// script/a.js
define(['require'],function(require){
    //函数定义区
    var showMessage = function(){
        require(['script/b'],function (b) {
          console.log("欢迎访问 " + b.getName());
        });
    };

    //暴露(返回)本模块API
    return {
        "showMessage": showMessage
    }
});

再次调用 b 模块的 doSomething 方法,可以发现运行成功了:

方法2:通过注入 exports 来解决
我们将模块 b 修改成如下代码:
// script/b.js
define(['script/a', 'exports'],function(a, exports){
    //函数定义区
    var doSomething = function(){
        a.showMessage();
    };

    var getName = function(){
        return "hangge.com";
    };

    //暴露本模块API
    exports.doSomething = doSomething;
    exports.getName = getName;
});

方法3:直接使用 CommonJS 规范定义模块
我们将模块 b 修改成如下代码:
// script/b.js
define(function(require, exports, module) {
  var a = require("script/a");

  //函数定义区
  var doSomething = function(){
      a.showMessage();
  };

  var getName = function(){
      return "hangge.com";
  };

  //暴露本模块API
  exports.doSomething = doSomething;
  exports.getName = getName;
});
 

八、错误处理

一般我们使用 RequireJS 时碰到的错误主要是 404(未找到)错误、网络超时或加载的脚本含有错误。RequireJS 提供了如下三种方式来处理这些错误。

1,require 的错误回调函数

下面代码先用 jQuery 库的一个 CDN 版本,如果其加载出错,则切换到本地版本。
require.config({
    //如果你打算支持Internet Explorer捕获加载错,并使用了define()或shim,则要将enforceDefine设置为true。
    enforceDefine: true,
    paths: {
        jquery: 'http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min'
    }
});

require(['jquery'], function ($) {
    //Do something with $ here
}, function (err) {
    //获取加载错误的模块
    var failedId = err.requireModules && err.requireModules[0];
    if (failedId === 'jquery') {
        //通过undef方法让所有依赖jQuery的模块都暂不加载,直到正确的jQuery被加载。
        requirejs.undef(failedId);
        //将jQuery设置为本地路径
        requirejs.config({
            paths: {
                jquery: 'local/jquery'
            }
        });
        //再次尝试加载jQuery
        require(['jquery'], function () {});
    } else {
        //处理其他模块错误
    }
});

2,通过"paths"数组配置

paths 配置项允许我们使用数组,下面代码先尝试加载 CDN 版本,如果出错,则退回到本地的 lib/jquery.js
requirejs.config({
    //如果你打算支持Internet Explorer捕获加载错,并使用了define()或shim,则要将enforceDefine设置为true。
    enforceDefine: true,
    paths: {
        jquery: [
            'http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min',
            //如果CDN文件加载失败,则自动加载本地版本
            'lib/jquery'
        ]
    }
});

//Later
require(['jquery'], function ($) {
  
});

3,全局的错误捕获:require.onError

为了捕获在局域的 errback 中未捕获的异常,可以重载 require.onError() 来实现全局的异常捕获。
require.onError = function (err) {
    console.log(err.requireType);
    if (err.requireType === 'timeout') {
        console.log('modules: ' + err.requireModules);
    }
    throw err;
};
评论

全部评论(0)

回到顶部