CommonJS和ES模块差异
它们有几个重大差异
-
CommonJS 模块输出的是一个值的拷贝; ES6 模块输出的是值的引用。
-
CommonJS 模块是运行时加载; ES6 模块是静态定义的,在编译时输出接口。
1. CommonJS 输出值的拷贝
// 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
模块加载以后,它的内部变化就影响不到输出的 mod.counter
了。这是因为 mod.counter
是一个原始类型的值,会被缓存。除非写成一个函数,才能得到内部变动后的值。
// lib.js
var counter = 3
function incCounter() {
counter++
}
module.exports = {
get counter() {
return counter
},
incCounter: incCounter,
}
2. ES6 模块输出值的引用
ES6
模块的运行机制与 CommonJS
不一样。JS 引擎对脚本静态分析的时候,遇到模块加载命令 import
,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。
// 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) // bar
setTimeout(() => console.log(foo), 500) // baz
3. 循环加载
CommonJS 模块的循环加载
CommonJS
模块的重要特性是加载时执行,即脚本代码在 require
的时候,就会全部执行。一旦出现某个模块被"循环加载",就只输出已经执行的部分,还未执行的部分不会输出。
// a.js
console.log('a starting')
exports.done = false
const b = require('./b.js')
console.log('in a, b.done = %j', b.done)
exports.done = true
console.log('a done')
上面代码之中,a.js
脚本先输出一个 done
变量,然后加载另一个脚本文件 b.js
。注意,此时 a.js
代码就停在这里,等待 b.js
执行完毕,再往下执行。
// b.js
console.log('b starting')
exports.done = false
const a = require('./a.js')
console.log('in b, a.done = %j', a.done)
exports.done = true
console.log('b done')
上面代码之中,b.js
执行到第二行,就会去加载 a.js
,这时,就发生了“循环加载”。系统会去 a.js
模块对应对象的 exports
属性取值,可是因为 a.js
还没有执行完,从 exports
属性只能取回已经执行的部分,而不是最后的值。
a.js
已经执行的部分,只有一行。
exports.done = false
因此,对于 b.js
来说,它从 a.js
只输入一个变量 done
,值为 false
。
然后,b.js
接着往下执行,等到全部执行完毕,再把执行权交还给 a.js
。于是,a.js
接着往下执行,直到执行完毕。我们写一个脚本 main.js
,验证这个过程。
// main.js
console.log('main starting')
const a = require('./a.js')
const b = require('./b.js')
console.log('in main, a.done = %j, b.done = %j', a.done, b.done)
执行 main.js
,运行结果如下。
main starting
a starting
b starting
in b, a.done = false
b done
in a, b.done = true
a done
in main, a.done = true, b.done = true