RequireJS 的 shim 用法

综述

RequireJS 遵循 AMD 规范(异步模块定义)。理论上来说,RequireJS 加载的模块必须符合 AMD 规范,使用 define() 函数定义。但是,由于历史原因,大量的 js 库并不符合规范。这样加载非规范的模块,就需要用到 shim

比如 underscore 和 backbone 都不符合 AMD 规范,如果要加载他们,就要像这样使用:

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

exports 是模块的返回值,该值一定要与库暴露的全局变量名称一致。
deps 表明模块的依赖,如果 A 模块依赖于 B 模块,而 A 模块不符合 AMD 规范,使用全局变量,那么 B 模块也必须使用全局变量,不然 A 模块会找不到 B 模块的依赖对象而报错。

准备

下面开始做实验。
File: test.html

<!DOCTYPE html>
<html>
<head>
    <title>shim</title>
    <script src="https://cdn.bootcss.com/require.js/2.3.5/require.min.js" data-main="main.js"></script>
</head>
<body>

</body>
</html>

File: a.js

// 立即执行函数,不暴露私有成员
(function(window){
    var a = {};
    a.sayHello = function(){
        alert("Hello this is A");
        return 'Hello';
    }
    window.a = a;
})(window)

File: main.js

require(['a'],function(a) {
    console.log(a);
});

载入文档,在 console 中我们会看到,输出 undefined
a is not defined

但是我们查看浏览器的 element 和 network 会发现,实际上 a.js 已经被加载进来了。
a.js has been loaded
a.js has been loaded network

下面我们在 console 中输入:

a.sayHello();

console 中输入命令

可以看出实际上 a.js 中暴露的全局变量还是存在的。

使用 shim

下面我们用 shim 让 RequireJS 可以获得暴露的全局变量作为返回值。

修改 main.js

requirejs.config({
    shim: {
        a:{
            deps:[],
            exports: 'a'
        }
    }
});
require(['a'],function(a) {
    console.log(a);
});

然后刷新文档,这次肯定是输出内容了。
exports 有效

如果我们把 exports: 'a' 改成 exports: 'something' 那么再刷新,一定会发现又输出 undefined

所以,exports 是模块的返回值,该值一定要与库暴露的全局变量名称一致。

使用 deps

下面来实验 deps 的用法,假设 a.js 依赖于 b.js

正确的写法

File: b.js

var b = {};

console.log('b');

b.string = 'Hello, this is a string from b.js';
b.getString = function() {
    return this.string;
}
b.setString = function(string) {
    this.string = string;
}

File: a.js

// 立即执行函数,不暴露私有成员
(function(window){
    console.log('a');

    var a = {};
    a.sayHello = function(){
        alert("Hello this is A");
        return 'Hello';
    }
    a.getBString = function() {
        alert(b.getString());
    }
    window.a = a;
})(window)

File:main.js

requirejs.config({
    shim: {
        a:{
            deps:['b'],
            exports: 'a'
        },
        b: {
            deps:[],
            exports: 'b'
        }
    }
});
require(['a'],function(aAlias) {

    aAlias.getBString();

});

效果如下:
depes 用法效果

这是标准的写法,下面我们来实验一个不好的写法。

错误的写法

将 main.js 改成

requirejs.config({
    shim: {
        a:{
            deps:[],
            exports: 'a'
        },
        b: {
            deps:[],
            exports: 'b'
        }
    }
});
require(['a','b'],function(aAlias, bAlias) {

    aAlias.getBString();

});

我们并没有声明 a.js 依赖 b.js 而是在使用的时候将 b.js require 进来了。这样在本例中也是正常的。但仅仅是因为本例太简单,对执行的先后顺序没有要求而已,换个场景可能就失效了。

require(['a','b'],function(aAlias, bAlias) 仅仅表示 a b 都加载完成后,执行回调函数,但是 加载的顺序是不固定的。执行的顺序是固定的,按照依赖声明的先后顺序执行。

在上面我们埋了 console.log('a)console.log('b') 那么在不好的写法中,控制台中打印的顺序是不固定的,有时候是 a b 有时候是 b a ,而正确的例子中,指定顺序是固定的,一定是 b a

下面是错误的写法,多次刷新的结果,可以看出,顺序是有变化的。
执行顺序不一定

参考文档

使用requireJS加载不符合AMD规范的js文件:shim的使用方式和实现原理
Javascript模块化编程(二):AMD规范

发表评论

电子邮件地址不会被公开。 必填项已用*标注