ECMAScript 6 入门教程—Module 的加载实现
1、浏览器加载
传统方法
代码是在模块作用域之中运行,而不是在全局作用域运行。模块内部的顶层变量,外部不可见。
模块脚本自动采用严格模式,不管有没有声明use strict。
模块之中,可以使用import命令加载其他模块(.js后缀不可省略,需要提供绝对 URL 或相对 URL),也可以使用export命令输出对外接口。
模块之中,顶层的this关键字返回undefined,而不是指向window。也就是说,在模块顶层使用this关键字,是无意义的。
同一个模块如果加载多次,将只执行一次。
import utils from 'https://example.com/js/utils.js';
const x = 1;
console.log(x === window.x); //false
console.log(this === undefined); // true
const isNotModuleScript = this !== undefined;
2、ES6 模块与 CommonJS 模块的差异
CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
CommonJS 模块的require()是同步加载模块,ES6 模块的import命令是异步加载,有一个独立的模块依赖的解析阶段。
// lib.js
var counter = 3;
function incCounter() {
counter++;
}
module.exports = {
counter: counter,
incCounter: incCounter,
};
// main.js
var mod = require('./lib');
console.log(mod.counter); // 3
mod.incCounter();
console.log(mod.counter); // 3
// lib.js
var counter = 3;
function incCounter() {
counter++;
}
module.exports = {
get counter() {
return counter
},
incCounter: incCounter,
};
$ node main.js
3
4
// lib.js
export let counter = 3;
export function incCounter() {
counter++;
}
// main.js
import { counter, incCounter } from './lib';
console.log(counter); // 3
incCounter();
console.log(counter); // 4
// m1.js
export var foo = 'bar';
setTimeout(() => foo = 'baz', 500);
// m2.js
import {foo} from './m1.js';
console.log(foo);
setTimeout(() => console.log(foo), 500);
$ babel-node m2.js
bar
baz
// lib.js
export let obj = {};
// main.js
import { obj } from './lib';
obj.prop = 123; // OK
obj = {}; // TypeError
// mod.js
function C() {
this.sum = 0;
this.add = function () {
this.sum += 1;
};
this.show = function () {
console.log(this.sum);
};
}
export let c = new C();
// x.js
import {c} from './mod';
c.add();
// y.js
import {c} from './mod';
c.show();
// main.js
import './x';
import './y';
$ babel-node main.js
1
3、Node.js 的模块加载方法
概述
{
"type": "module"
}
# 解释成 ES6 模块
$ node my-app.js
package.json 的 main 字段
// ./node_modules/es-module-package/package.json
{
"type": "module",
"main": "./src/index.js"
}
// ./my-app.mjs
import { something } from 'es-module-package';
// 实际加载的是 ./node_modules/es-module-package/src/index.js
package.json 的 exports 字段
// ./node_modules/es-module-package/package.json
{
"exports": {
"./submodule": "./src/submodule.js"
}
}
import submodule from 'es-module-package/submodule';
// 加载 ./node_modules/es-module-package/src/submodule.js
// ./node_modules/es-module-package/package.json
{
"exports": {
"./features/": "./src/features/"
}
}
import feature from 'es-module-package/features/x.js';
// 加载 ./node_modules/es-module-package/src/features/x.js
// 报错
import submodule from 'es-module-package/private-module.js';
// 不报错
import submodule from './node_modules/es-module-package/private-module.js';
{
"exports": {
".": "./main.js"
}
}
// 等同于
{
"exports": "./main.js"
}
{
"main": "./main-legacy.cjs",
"exports": {
".": "./main-modern.cjs"
}
}
{
"type": "module",
"exports": {
".": {
"require": "./main.cjs",
"default": "./main.js"
}
}
}
{
"exports": {
"require": "./main.cjs",
"default": "./main.js"
}
}
{
// 报错
"exports": {
"./feature": "./lib/feature.js",
"require": "./main.cjs",
"default": "./main.js"
}
}
CommonJS 模块加载 ES6 模块
(async () => {
await import('./my-app.mjs');
})();
ES6 模块加载 CommonJS 模块
// 正确
import packageMain from 'commonjs-package';
// 报错
import { method } from 'commonjs-package';
import packageMain from 'commonjs-package';
const { method } = packageMain;
// cjs.cjs
module.exports = 'cjs';
// esm.mjs
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const cjs = require('./cjs.cjs');
cjs === 'cjs'; // true
同时支持两种格式的模块
import cjsModule from '../index.js';
export const foo = cjsModule.foo;
"exports":{
"require": "./index.js",
"import": "./esm/wrapper.js"
}
Node.js 的内置模块
// 整体加载
import EventEmitter from 'events';
const e = new EventEmitter();
// 加载指定的输出项
import { readFile } from 'fs';
readFile('./foo.txt', (err, source) => {
if (err) {
console.error(err);
} else {
console.log(source);
}
});
加载路径
// ES6 模块中将报错
import { something } from './index';
import './foo.mjs?query=1'; // 加载 ./foo 传入参数 ?query=1
内部变量
arguments
require
module
exports
__filename
__dirname
4、循环加载
// a.js
var b = require('b');
// b.js
var a = require('a');
CommonJS 模块的加载原理
{
id: '...',
exports: { ... },
loaded: true,
...
}
CommonJS 模块的循环加载
exports.done = false;
var b = require('./b.js');
console.log('在 a.js 之中,b.done = %j', b.done);
exports.done = true;
console.log('a.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 执行完毕');
exports.done = false;
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
exports.done = true;
var a = require('a'); // 安全的写法
var foo = require('a').foo; // 危险的写法
exports.good = function (arg) {
return a.foo('good', arg); // 使用的是 a.foo 的最新值
};
exports.bad = function (arg) {
return foo('bad', arg); // 使用的是一个部分加载时的值
};
ES6 模块的循环加载
// 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
// a.mjs
import {bar} from './b';
console.log('a.mjs');
console.log(bar());
function foo() { return 'foo' }
export {foo};
// b.mjs
import {foo} from './a';
console.log('b.mjs');
console.log(foo());
function bar() { return 'bar' }
export {bar};
$ node --experimental-modules a.mjs
b.mjs
foo
a.mjs
bar
// a.mjs
import {bar} from './b';
console.log('a.mjs');
console.log(bar());
const foo = () => 'foo';
export {foo};
// even.js
import { odd } from './odd'
export var counter = 0;
export function even(n) {
counter++;
return n === 0 || odd(n - 1);
}
// odd.js
import { even } from './even';
export function odd(n) {
return n !== 0 && even(n - 1);
}
$ babel-node
> import * as m from './even.js';
> m.even(10);
true
> m.counter
6
> m.even(20)
true
> m.counter
17
// even.js
var odd = require('./odd');
var counter = 0;
exports.counter = counter;
exports.even = function (n) {
counter++;
return n == 0 || odd(n - 1);
}
// odd.js
var even = require('./even').even;
module.exports = function (n) {
return n != 0 && even(n - 1);
}
$ node
> var m = require('./even');
> m.even(10)
TypeError: even is not a function
评论