Module

传统js文件加载

1
2
3
4
5
6
7
8
9
10
11
12
// 传统异步加载
<script src="path/to/myModule.js" defer></script>
<script src="path/to/myModule.js" async></script>

defer要等到整个页面在内存中正常渲染结束(DOM 结构完全生成,以及其他脚本执行完成),才会执行;
async一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染。

defer是“渲染完再执行”,async是“下载完就执行”。
另外,如果有多个defer脚本,会按照它们在页面出现的顺序加载,而多个async脚本是不能保证加载顺序的。

// 加载es6模块 异步加载 ,等同于defer
<script type="module" src="./foo.js"></script>

AMD - Asynchronous Module Definition 异步加载

一般应用在浏览器端。流行的浏览器端异步加载库 RequireJS 实现的就是AMD规范。
AMD是依赖前置, 对于依赖的模块提前执行

1
2
3
4
5
6
7
define(id?:string, [depends]?, function(depends?) {
// expose public methods
return obj;
});

require([module], function(module?){
});
1
2
3
4
5
6
7
8
9
10
11
12
requirejs.config({
baseUrl: "./lib/",
paths: {
echarts: "js/echarts.min",
chart: "../js/chart",
},
shim: {
chart: ["echarts"]
}
});
requirejs(["jquery"], function($) {});
require(["jquery"], function($) {});

CMD - Common Module Definition 异步加载

一般也是用在浏览器端。浏览器端异步加载库 Sea.js 实现的就是CMD规范。
CMD依赖就近,只在需要用到某个模块的时候再require, 对于依赖的模块延迟执行

1
2
3
4
5
6
7
8
9
10
11
12
//所有模块都通过define来定义
define(function(require, exports, module) {
// 通过require引入依赖
var a = require('./a');
a.doSomething();
var b = require('./b');
b.doSomething();
exports.doSomething = ...
module.exports = ...
})

seajs.use(id, callback?)

CommonJS - module.exports/require 同步加载

根据CommonJS规范,一个单独的文件就是一个模块。每一个模块都是一个单独的作用域,也就是说,在一个文件定义的变量(还包括函数和类),都是私有的,对其他文件是不可见的。

模块加载的顺序,按照其在代码中出现的顺序。

CommonJS 模块输出的是值的拷贝(原始值的拷贝),也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。

CommonJS是一个更偏向于服务器端的规范。NodeJS采用了这个规范。
CommonJS的一个模块就是一个脚本文件。require命令第一次加载该脚本时就会执行整个脚本,然后在内存中生成一个对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
{
id: '...',
exports: { ... },
loaded: true,
...
}

module.id 模块的识别符,通常是带有绝对路径的模块文件名。
module.filename 模块的文件名,带有绝对路径。
module.loaded 返回一个布尔值,表示模块是否已经完成加载。
module.parent 返回一个对象,表示调用该模块的模块。
module.children 返回一个数组,表示该模块要用到的其他模块。
module.exports 表示模块对外输出的值。

以后需要用到这个模块的时候,就会到exports属性上面取值。即使再次执行require命令,也不会再次执行该模块,而是到缓存之中取值。也就是说,CommonJS 模块无论加载多少次,都只会在第一次加载时运行一次,以后再加载,就返回第一次运行的结果,除非手动清除系统缓存。

如果要处处获取到模块内的最新值的话,也可以每次更新数据的时候每次都要去更新 module.exports 上的值

1
2
3
4
5
6
7
// 删除指定模块的缓存
delete require.cache[moduleName];

// 删除所有模块的缓存
Object.keys(require.cache).forEach(function(key) {
  delete require.cache[key];
})

ES6 - export/import

ES modules export live bindings, not values, so values can be changed after they are initially imported

import变量都是只读的,因为它的本质是输入接口,但是如果变量是对象,改写对象的属性是允许的,但是这种写法很难查错。建议凡是输入的变量,都当做完全只读。

ES6模块自动采用严格模式,不管有没有在模块头部加上”use strict”;

模块不会重复执行,无论是 ES6 模块还是 CommonJS 模块,当你重复引入某个相同的模块时,模块只会执行一次。
CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# Import a specific item from a source module, with its original name.
import { something } from './module.js';

# Import a specific item from a source module, with a custom name assigned upon import.
import { something as somethingElse } from './module.js';

# Import everything from the source module as an object which exposes all the source module's named exports as properties and methods.
import * as module from './module.js'

# Import the default export of the source module.
import something from './module.js';

# Load the module code, but don't make any new objects available.
# This is useful for polyfills, or when the primary purpose of the imported code is to muck about with prototypes.
import './module.js';

# Import modules using the dynamic import API.
# This is useful for code-splitting applications and using modules on-the-fly.
import('./modules.js').then(({ default: DefaultExport, NamedExport })=> {
// do something with modules.
})

# Export a value that has been previously declared
const something = true;
export { something };

# Rename on export
export { something as somethingElse };

# Export a value immediately upon declaration
// this works with `var`, `let`, `const`, `class`, and `function`
export const something = true;

# Export a single value as the source module's default export
# This practice is only recommended if your source module only has one export.
# It is bad practice to mix default and named exports in the same module, though it is allowed by the specification.
export default something;

因为编译时静态分析,导致了我们无法在条件语句或者拼接字符串模块,因为这些都是需要在运行时才能确定的结果在 ES6 模块是不被允许的,所以 动态引入 import() 应运而生。

1
2
3
4
5
6
7
8
9
10
async function main() {
const myModule = await import('./myModule.js');
const {export1, export2} = await import('./myModule.js');
const [module1, module2, module3] =
await Promise.all([
import('./module1.js'),
import('./module2.js'),
import('./module3.js'),
]);
}

UMD是AMD和CommonJS的糅合 - Universal Module Definition

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// amdWeb.js

// Uses AMD or browser globals to create a module.

// If you want something that will also work in Node, see returnExports.js
// If you want to support other stricter CommonJS environments,
// or if you need to create a circular dependency, see commonJsStrict.js

// Defines a module "amdWeb" that depends on another module called "b".
// Note that the name of the module is implied by the file name. It is best
// if the file name and the exported global have matching names.

// If the 'b' module also uses this type of boilerplate, then
// in the browser, it will create a global .b that is used below.

// If you do not want to support the browser global path, then you
// can remove the `root` use and the passing of `this` as the first arg to
// the top function.

(function(root, factory) {
if (typeof define === "function" && define.amd) {
// AMD. Register as an anonymous module.
define(["b"], factory);
} else {
// Browser globals
root.amdWeb = factory(root.b);
}
})(typeof self !== "undefined" ? self : this, function(b) {
// Use b in some fashion.

// Just return a value to define the module export.
// This example returns an object, but the module
// can return a function as the exported value.
return {};
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// amdWebGlobal.js

// Uses AMD or browser globals to create a module. This example creates a
// global even when AMD is used. This is useful if you have some scripts
// that are loaded by an AMD loader, but they still want access to globals.
// If you do not need to export a global for the AMD case, see amdWeb.js.

// If you want something that will also work in Node, and still export a
// global in the AMD case, see returnExportsGlobal.js
// If you want to support other stricter CommonJS environments,
// or if you need to create a circular dependency, see commonJsStrictGlobal.js

// Defines a module "amdWebGlobal" that depends another module called "b".
// Note that the name of the module is implied by the file name. It is best
// if the file name and the exported global have matching names.

// If the 'b' module also uses this type of boilerplate, then
// in the browser, it will create a global .b that is used below.

(function(root, factory) {
if (typeof define === "function" && define.amd) {
// AMD. Register as an anonymous module.
define(["b"], function(b) {
// Also create a global in case some scripts
// that are loaded still are looking for
// a global even when an AMD loader is in use.
return (root.amdWebGlobal = factory(b));
});
} else {
// Browser globals
root.amdWebGlobal = factory(root.b);
}
})(typeof self !== "undefined" ? self : this, function(b) {
// Use b in some fashion.

// Just return a value to define the module export.
// This example returns an object, but the module
// can return a function as the exported value.
return {};
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// commonjsAdapter.js

// Defines a module that works in CommonJS and AMD.

// This version can be used as common boilerplate for a library module
// that you only want to expose to CommonJS and AMD loaders. It will not work
// well for defining browser globals.

// If you only want to target Node and AMD or a CommonJS environment that
// supports assignment to module.exports and you are not defining a module
// that has a circular dependency, see nodeAdapter.js

// Help Node out by setting up define.
if (
typeof exports === "object" &&
typeof exports.nodeName !== "string" &&
typeof define !== "function"
) {
var define = function(factory) {
factory(require, exports, module);
};
}

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

// Only attach properties to the exports object to define
// the module's properties.
exports.action = function() {};
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// commonjsStrict.js

// Uses CommonJS, AMD or browser globals to create a module.

// If you just want to support Node, or other CommonJS-like environments that
// support module.exports, and you are not creating a module that has a
// circular dependency, then see returnExports.js instead. It will allow
// you to export a function as the module value.

// Defines a module "commonJsStrict" that depends another module called "b".
// Note that the name of the module is implied by the file name. It is best
// if the file name and the exported global have matching names.

// If the 'b' module also uses this type of boilerplate, then
// in the browser, it will create a global .b that is used below.

// If you do not want to support the browser global path, then you
// can remove the `root` use and the passing `this` as the first arg to
// the top function.

(function(root, factory) {
if (typeof define === "function" && define.amd) {
// AMD. Register as an anonymous module.
define(["exports", "b"], factory);
} else if (
typeof exports === "object" &&
typeof exports.nodeName !== "string"
) {
// CommonJS
factory(exports, require("b"));
} else {
// Browser globals
factory((root.commonJsStrict = {}), root.b);
}
})(typeof self !== "undefined" ? self : this, function(exports, b) {
// Use b in some fashion.

// attach properties to the exports object to define
// the exported module properties.
exports.action = function() {};
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// commonjsStrictGlobal.js

// Uses CommonJS, AMD or browser globals to create a module. This example
// creates a global even when AMD is used. This is useful if you have some
// scripts that are loaded by an AMD loader, but they still want access to
// globals. If you do not need to export a global for the AMD case, see
// commonjsStrict.js.

// If you just want to support Node, or other CommonJS-like environments that
// support module.exports, and you are not creating a module that has a
// circular dependency, then see returnExportsGlobal.js instead. It will allow
// you to export a function as the module value.

// Defines a module "commonJsStrictGlobal" that depends another module called
// "b". Note that the name of the module is implied by the file name. It is
// best if the file name and the exported global have matching names.

// If the 'b' module also uses this type of boilerplate, then
// in the browser, it will create a global .b that is used below.

(function(root, factory) {
if (typeof define === "function" && define.amd) {
// AMD. Register as an anonymous module.
define(["exports", "b"], function(exports, b) {
factory((root.commonJsStrictGlobal = exports), b);
});
} else if (
typeof exports === "object" &&
typeof exports.nodeName !== "string"
) {
// CommonJS
factory(exports, require("b"));
} else {
// Browser globals
factory((root.commonJsStrictGlobal = {}), root.b);
}
})(typeof self !== "undefined" ? self : this, function(exports, b) {
// Use b in some fashion.

// attach properties to the exports object to define
// the exported module properties.
exports.action = function() {};
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// jqueryPlugin.js

// Uses CommonJS, AMD or browser globals to create a jQuery plugin.

(function(factory) {
if (typeof define === "function" && define.amd) {
// AMD. Register as an anonymous module.
define(["jquery"], factory);
} else if (typeof module === "object" && module.exports) {
// Node/CommonJS
module.exports = function(root, jQuery) {
if (jQuery === undefined) {
// require('jQuery') returns a factory that requires window to
// build a jQuery instance, we normalize how we use modules
// that require this pattern but the window provided is a noop
// if it's defined (how jquery works)
if (typeof window !== "undefined") {
jQuery = require("jquery");
} else {
jQuery = require("jquery")(root);
}
}
factory(jQuery);
return jQuery;
};
} else {
// Browser globals
factory(jQuery);
}
})(function($) {
$.fn.jqueryPlugin = function() {
return true;
};
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// nodeAdapter.js

// Defines a module that works in Node and AMD.

// This version can be used as common boilerplate for a library module
// that you only want to expose to Node and AMD loaders. It will not work
// well for defining browser globals.

// If you need a version of this file that works CommonJS-like environments
// that do not support module.exports or if you want to define a module
// with a circular dependency, see commonjsAdapter.js

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

return function() {};
});
})(
// Help Node out by setting up define.
typeof module === "object" && module.exports && typeof define !== "function"
? function(factory) {
module.exports = factory(require, exports, module);
}
: define
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// returnExports.js

// Uses Node, AMD or browser globals to create a module.

// If you want something that will work in other stricter CommonJS environments,
// or if you need to create a circular dependency, see commonJsStrict.js

// Defines a module "returnExports" that depends another module called "b".
// Note that the name of the module is implied by the file name. It is best
// if the file name and the exported global have matching names.

// If the 'b' module also uses this type of boilerplate, then
// in the browser, it will create a global .b that is used below.

// If you do not want to support the browser global path, then you
// can remove the `root` use and the passing `this` as the first arg to
// the top function.

(function(root, factory) {
if (typeof define === "function" && define.amd) {
// AMD. Register as an anonymous module.
define(["b"], factory);
} else if (typeof module === "object" && module.exports) {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = factory(require("b"));
} else {
// Browser globals (root is window)
root.returnExports = factory(root.b);
}
})(typeof self !== "undefined" ? self : this, function(b) {
// Use b in some fashion.

// Just return a value to define the module export.
// This example returns an object, but the module
// can return a function as the exported value.
return {};
});

// if the module has no dependencies, the above pattern can be simplified to
(function(root, factory) {
if (typeof define === "function" && define.amd) {
// AMD. Register as an anonymous module.
define([], factory);
} else if (typeof module === "object" && module.exports) {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = factory();
} else {
// Browser globals (root is window)
root.returnExports = factory();
}
})(typeof self !== "undefined" ? self : this, function() {
// Just return a value to define the module export.
// This example returns an object, but the module
// can return a function as the exported value.
return {};
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// returnExportsGlobal.js

// Uses Node, AMD or browser globals to create a module. This example creates
// a global even when AMD is used. This is useful if you have some scripts
// that are loaded by an AMD loader, but they still want access to globals.
// If you do not need to export a global for the AMD case,
// see returnExports.js.

// If you want something that will work in other stricter CommonJS environments,
// or if you need to create a circular dependency, see commonJsStrictGlobal.js

// Defines a module "returnExportsGlobal" that depends another module called
// "b". Note that the name of the module is implied by the file name. It is
// best if the file name and the exported global have matching names.

// If the 'b' module also uses this type of boilerplate, then
// in the browser, it will create a global .b that is used below.

(function(root, factory) {
if (typeof define === "function" && define.amd) {
// AMD. Register as an anonymous module.
define(["b"], function(b) {
return (root.returnExportsGlobal = factory(b));
});
} else if (typeof module === "object" && module.exports) {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = factory(require("b"));
} else {
// Browser globals
root.returnExportsGlobal = factory(root.b);
}
})(typeof self !== "undefined" ? self : this, function(b) {
// Use b in some fashion.

// Just return a value to define the module export.
// This example returns an object, but the module
// can return a function as the exported value.
return {};
});
CommonJS的循环加载
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# a.js
exports.done = false;
var b = require('./b.js');
console.log('在 a.js 之中,b.done = %j', b.done);
exports.done = true;
console.log('a.js 执行完毕');

# b.js
exports.done = false;
var a = require('./a.js');
console.log('在 b.js 之中,a.done = %j', a.done);
exports.done = true;
console.log('b.js 执行完毕');

# main.js
var a = require('./a.js');
var b = require('./b.js');
console.log('在 main.js 之中, a.done=%j, b.done=%j', a.done, b.done);

# node main.js
在 b.js 之中,a.done = false
b.js 执行完毕
在 a.js 之中,b.done = true
a.js 执行完毕
在 main.js 之中, a.done=true, b.done=true

上面的代码证明了两件事
一是,在b.js之中,a.js没有执行完毕,只执行了第一行
二是,main.js执行到第二行时,不会再次执行b.js

1
2
3
4
5
6
7
8
9
10
11
12
// 安全的写法
var b = require('b');
// 危险的写法,如果发生循环加载,require('b').foo的值很可能后面会被改写
// var foo = require('b').foo;

exports.good = function (arg) {
return b.foo('good', arg); // 使用的是 b.foo 的最新值
};

exports.bad = function (arg) {
return foo('bad', arg); // 使用的是一个部分加载时的值
};
ES6的循环加载

ES6 处理“循环加载”与 CommonJS 有本质的不同。ES6 模块是动态引用,如果使用import从一个模块加载变量(即import foo from ‘foo’),那些变量不会被缓存,而是成为一个指向被加载模块的引用,需要开发者自己保证,真正取值的时候能够取到值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// a.mjs
import { bar } from './b';
console.log('a.mjs');
console.log(bar);
export let foo = 'foo';

// b.mjs
import { foo } from './a';
console.log('b.mjs');
console.log(foo);
export let bar = 'bar';

$ node --experimental-modules a.mjs
b.mjs
ReferenceError: foo is not defined