ECMAScript6面试考点之CommonJS模块和ES6模块
1、CommonJS
第一种导出方式是将需要导出的函数或变量存储到 module.exports 里面,其中 module.exports 原本是一个空对象;
第二种导出方式是 exports.变量 或 exports.函数。exports 在内部其实是指向了 module.exports,所以当我们执行 exports.变量 或 exports.函数 时,其实就相当于把变量或函数存储到 module.exports 中。在使用第二种导出方式时,不能对 exports 进行重新赋值,否则就将 module.exports 直接全部覆盖了。
导入方式require()。
1)对于基本数据类型,属于复制。即会被模块缓存。同时,在另一个模块可以对该模块输出的变量重新赋值。
b模块export的count变量,是一个复制行为。在plusCount方法调用之后,a模块中的count不受影响。
2)对于复杂数据类型,属于浅拷贝。由于两个模块引用的对象指向同一个内存空间,因此对该模块的值做修改时会影响另一个模块。
对于对象来说属于浅拷贝。当执行a模块时,首先打印obj.count的值为1,然后通过plusCount方法,再次打印时为2。接着在a模块修改count的值为3,此时在b模块的值也为3。
3)当使用require命令加载某个模块时,就会运行整个模块的代码。
4)当使用require命令加载同一个模块时,不会再执行该模块,而是取到缓存之中的值。也就是说,CommonJS模块无论加载多少次,都只会在第一次加载时运行一次,以后再加载,就返回第一次运行的结果,除非手动清除系统缓存。
5)循环加载时,属于加载时执行。即脚本代码在require的时候,就会全部执行。一旦出现某个模块被"循环加载",就只输出已经执行的部分,还未执行的部分不会输出。
a、在Node.js中执行c模块。此时遇到require关键字,执行a.js中代码。
b、a模块只执行exports.done = false这条语句。在a模块中exports之后,通过require引入了b模块,执行b模块的代码。
c、b模块执行exports.done = false这条语句。在b模块中exports之后,又require引入了a模块,此时a模块已经引入过,且a模块只执行exports.done = false这条语句。 (就只输出已经执行的部分,还未执行的部分不会输出。)
d、回到b模块,打印b.js-1,false 、b.js-2 执行完毕。b模块执行完毕。回到a模块,接着打印a.js-1 true、a.js-2 执行完毕。a模块执行完毕回到c模块,接着执行require,需要引入b模块。由于在a模块中已经引入过了,所以直接就可以输出值了 c.js-1执行完毕truetrue 。结束。
2、ES6模块
export命令用于输出模块的对外接口,import命令用于引入其他模块提供的功能接口。
export写法
exportimport写法
importexport与import的复合写法
如果在某个模块中引入了其他模块,又导出了该模块,可以采用export和import的复合写法
1)ES6模块中的值属于【动态只读引用】。
a、对于只读来说,即不允许修改引入变量的值,import的变量是只读的,不论是基本数据类型还是复杂数据类型。当模块遇到import命令时,就会生成一个只读引用。等到脚本真正执行时,再根据这个只读引用,到被加载的那个模块里面去取值。
b、对于动态来说,原始值发生变化,import加载的值也会发生变化。不论是基本数据类型还是复杂数据类型。
虽然不能将counter重新赋值一个新的对象,但是可以给对象添加属性和方法。此时不会报错。这种行为类型与关键字const的用法。
循环加载时,ES6模块是动态引用。只要两个模块之间存在某个引用,代码就能够执行。
ES6 模块不会缓存运行结果,而是动态地去被加载的模块取值,并且变量总是绑定其所在的模块。
总结:
CommonJS 模块输出的是一个值的拷贝,ES6 模块输出的是值的引用。
CommonJS 模块是运行时加载,ES6 模块是编译时输出接口。
CommonJS 加载的是一个对象(即module.exports属性),该对象只有在脚本运行完才会生成。而 ES6 模块不是对象,它的对外接口只是一种静态定义,在代码静态解析阶段就会生成。
CommonJS 模块的require()是同步加载模块,ES6 模块的import命令是异步加载,有一个独立的模块依赖的解析阶段。