ES6模块和CommonJS模块

1,773 阅读2分钟

具体的两大差异如下

  • CommonJS模块输出的是一个值的复制,ES6模块输出的是值的引用
  • CommonJS模块是运行时加载,ES6模块是编译时输出接口

第一个差异的原因:

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

ES6模块的运行机制与CommonJS不一样。JS引擎对脚本静态分析的时候,遇到模块加载命令import就会生成一个只读引用。等到脚本真正执行的时候,再根据这个只读引用到被加载的模块中取值

//lib.js
export let counter = 3
export function incCounter() {
    counter++
}

//main.js
import { counter, incCounter } from './lib.js'
console.log(counter)    //3
incCounter()
console.log(counter)    //4

所以,ES6模块不会缓存结果,而是动态的去被加载的模块取值,并且变量总是绑定其所在的模块。

由于ES6输入的模块变量只是一个“符号连接”,所以这个变量是只读的,对它进行重新赋值会报错。

ES6模块中,顶层的this指向undifined, CommonJS模块的顶层this指向当前模块

tips: 提一下commonJS模块加载的原理

CommonJS的一个模块就是一个脚本文件。require命令第一次加载该脚本时就会执行整个脚本,然后在内存中生成一个对象。

Module {
    id: '...',
    exports: { ... },
    loaded: true,
    ...
}

其中loaded表示该模块的脚本是否执行完毕。以后需要用到这个模块时就回到exports属性上面取值。即使再次执行require命令,也不会再次执行该模块,而是到缓存中取值。

从代码和图中可以看到,module.exports和exports指向的同一块内存空间,而当直接给module.exports或exports赋值时,就相当于改变了内存,两者指代的就不是同一内存,这样就会导致exports中的内容失效,因为module.exports所指向的内存永远是真正的内存

有一种赋值特殊就是,exports = module.exports 这个用来重新建立引用关系的

就是重新把无效内存指向真正内存 这样 exports 和 module.exports 就重新建立了联系