此规范解决“如何编写能够在交互式模块化系统中使用的模块”。这个模块化系统可以是客户端系统或服务端系统、安全的系统或不安全系统、现在已有的系统或通过语法扩展而被支持的未来系统。这些模块不仅在各自的顶级作用域内拥有一些私有内容,而且能够从其他模块中导入单例对象,并能够导出自己内部的 API 给其他模块使用。换一种方式来说,此规范定义了一个模块化系统为了支持交互式模块所需要的最基本特性。
协议
模块上下文(Module Context)
在一个模块中,有一个变量 require, 值指向了一个函数。
这个 require 函数接收一个参数:模块的标识符。
require 函数返回外部模块(即 1 中 模块的标识符 对应的模块)导出的 API。
依赖循环/闭环(dependency cycle) 的定义:一个模块 B 在完成它的初始化[2] 之前,require(依赖) 了一个此次依赖链中较为靠前的模块 A,此时发生的就叫做依赖循环。如果发生了依赖循环,那么模块 A 必须在模块 B 初始化之前,导出了模块 B 初始化所需要的内容。(有点绕:可以看下 CommonJS in NodeJS#循环依赖 中的我的个人理解)
As a guideline, if the relationship between exports and module.exports seems like magic to you, ignore exports and only use module.exports. 如果你不清楚 exports 和 module.exports 之间的关系,那就不要用 exports 了,只管用 module.exports 就行了。
If there is a dependency cycle, the foreign module may not have finished executing at the time it is required by one of its transitive dependencies; in this case, the object returned by “require” must contain at least the exports that the foreign module has prepared before the call to require that led to the current module’s execution. 如果出现依赖闭环(dependency cycle),那么外部模块在被它的传递依赖(transitive dependencies)所 require 的时候可能并没有执行完成;在这种情况下,”require”返回的对象必须至少包含此外部模块在调用 require 函数(会进入当前模块执行环境)之前就已经准备完毕的输出。
// a.js console.log("a start"); module.exports.name = "a"; let b = require("./b"); console.log("a required b is:", b); module.exports.b_required = true; console.log("a end");
// b.js console.log("b start"); module.exports.name = "b"; let a = require("./a"); console.log("b required a is:", a); module.exports.a_required = true; console.log("b end");
// main.js console.log("main start"); let a = require("./a"); console.log("main required a is:", a); let b = require("./b"); console.log("main required b is:", b);
// 执行 node main.js
执行:node main.js 可先按照自己的理解写一下打印顺序。 下面是输出结果:
1 2 3 4 5 6 7 8 9
main start a start b start b required a is: { name: 'a' } b end a required b is: { name: 'b', a_required: true } a end main required a is: { name: 'a', b_required: true } main required b is: { name: 'b', a_required: true }
如果你的答案和上面一样,那恭喜你了。如果不太一样,可以看下我的理解:
依赖闭环仅可能发生在 依赖/模块 的执行过程中(即第一次引用 依赖/模块 的时候)。
这个例子中的依赖链条是这样的:
main->a->b->a(产生了闭环,因为 a 和 b 都是第一次引用)
main->b(b 在第一次已经执行过了,此次并没有发生执行,所以不会产生闭环)
在 a->b->a 执行过程中,a 执行到 require(‘./b’) 的时候,会去执行 b 以期获得 b。
b 执行到一半的时候,引用了 a。因为 b 此次依赖执行的祖先模块中有 a(意思就是 a 还没有执行完),于是发现了依赖闭环。
于是,b 中对 a 的引用,便只返回 a 中已执行的部分(即 require(‘./b’) 之前的内容)。
This is equivalent to the JavaScript property __proto__ which is non-standard but de-facto implemented by many browsers. 这个等同于 JavaScript 的非标准但许多浏览器实现的属性 __proto__。
Running this code has exactly the same effect as the previous example of the init() function above; what’s different — and interesting — is that the displayName() inner function is returned from the outer function before being executed.