35.前端基础-模块化
9.1 CommonJS规范
参考答案:
CommonJS规范加载模块是同步的,只有加载完成,才能执行后面的操作。
CommonJS
规范中的module
、exports
和require
- 每个文件就是一个模块,有自己的作用域。每个模块内部,
module
变量代表当前模块,是一个对象,它的exports
属性(即module.exports
)是对外的接口。 module.exports
属性表示当前模块对外输出的接口,其他文件加载该模块,实际上就是读取module.exports
变量。- 为了方便,
Node
为每个模块提供一个exports
变量,指向module.exports
。
let exports = module.exports;
require
命令用于加载模块文件。
使用示例:
//name.js exports.name = function(){return '李婷婷'}; //导出 //getName.js let getName = require('name'); //引入
注:不能直接将exports
变量指向一个值,因为这样等于切断了exports
与module.exports
的联系:如下
exports = function(x){console.log(x)}
如果一个模块的对外接口,就是一个单一的值,不能使用exports
输出,只能使用module.exports
输出。
CommonJS模块导入用require
,导出用module.exports
。导出的对象需注意,如果是静态值,而且非常量,后期可能会有所改动的,请使用函数动态获取,否则无法获取修改值。导入的参数,是可以随意改动的,所以使用时要注意
9.2 ES6 module 和 CommonJS module 的区别
参考答案:
为CommonJS的
require
语法是同步的,所以就导致了CommonJS模块规范只适合用在服务端,而ES6模块无论是在浏览器端还是服务端都是可以使用的,但是在服务端中,还需要遵循一些特殊的规则才能使用 ;CommonJS 模块输出的是一个值的拷贝,而ES6 模块输出的是值的引用;
CommonJS 模块是运行时加载,而ES6 模块是编译时输出接口,使得对JS的模块进行静态分析成为了可能
因为两个模块加载机制的不同,所以在对待循环加载的时候,它们会有不同的表现。CommonJS遇到循环依赖的时候,只会输出已经执行的部分,后续的输出或者变化,是不会影响已经输出的变量。而ES6模块相反,使用
import
加载一个变量,变量不会被缓存,真正取值的时候就能取到最终的值;关于模块顶层的
this
指向问题,在CommonJS顶层,this
指向当前模块;而在ES6模块中,this
指向undefined
;关于两个模块互相引用的问题,在ES6模块当中,是支持加载CommonJS模块的。但是反过来,CommonJS并不能
require
ES6模块,在NodeJS中,两种模块方案是分开处理的。
9.3 ES6 module、CommonJS module 循环引用的问题
参考答案:
循环加载指的是a脚本的执行依赖b脚本,b脚本的执行依赖a脚本
CommonJS模块是加载时执行。一旦出现某个模块被“循环加载”,就只输出已经执行的部分,没有执行的部分不会输出。
ES6模块对导出模块,变量,对象是动态引用,遇到模块加载命令import时不会去执行模块,只是生成一个指向被加载模块的引用。
CommonJS模块规范主要适用于后端Node.js,后端Node.js是同步模块加载,所以在模块循环引入时模块已经执行完毕。推荐前端工程中使用ES6的模块规范,通过安装Babel转码插件支持ES6模块引入的语法。
解析:
- CommonJS模块的加载原理
CommonJS模块就是一个脚本文件,require命令第一次加载该脚本时就会执行整个脚本,然后在内存中生成该模块的一个说明对象。
{ id: '', //模块名,唯一 exports: { //模块输出的各个接口 ... }, loaded: true, //模块的脚本是否执行完毕 ... }
以后用到这个模块时,就会到对象的exports属性中取值。即使再次执行require命令,也不会再次执行该模块,而是到缓存中取值。
CommonJS模块是加载时执行,即脚本代码在require时就全部执行。一旦出现某个模块被“循环加载”,就只输出已经执行的部分,没有执行的部分不会输出。
案例说明:
案例来源于Node官方说明:nodejs.org/api/modules…
//a.js exports.done = false; var b = require('./b.js'); console.log('在a.js中,b.done = %j', b.done); exports.done = true; console.log('a.js执行完毕!') //b.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执行完毕!') //main.js 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 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
JS代码执行顺序如下:
1)main.js中先加载a.js,a脚本先输出done变量,值为false,然后加载b脚本,a的代码停止执行,等待b脚本执行完成后,才会继续往下执行。
2)b.js执行到第二行会去加载a.js,这时发生循环加载,系统会去a.js模块对应对象的exports属性取值,因为a.js没执行完,从exports属性只能取回已经执行的部分,未执行的部分不返回,所以取回的值并不是最后的值。
3)a.js已执行的代码只有一行,exports.done = false;所以对于b.js来说,require a.js只输出了一个变量done,值为false。往下执行console.log('在b.js中,a.done = %j', a.done);控制台打印出:
在b.js中,a.done = false
4)b.js继续往下执行,done变量设置为true,console.log('b.js执行完毕!'),等到全部执行完毕,将执行权交还给a.js。此时控制台输出:
b.js执行完毕!
5)执行权交给a.js后,a.js接着往下执行,执行console.log('在a.js中,b.done = %j', b.done);控制台打印出:
在a.js中,b.done = true
6)a.js继续执行,变量done设置为true,直到a.js执行完毕。
a.js执行完毕!
7)main.js中第二行不会再次执行b.js,直接输出缓存结果。最后控制台输出:
在main.js中,a.done = true, b.done = true
总结:
1)在b.js中,a.js没有执行完毕,只执行了第一行,所以循环加载中,只输出已执行的部分。
2)main.js第二行不会再次执行,而是输出缓存b.js的执行结果。exports.done = true;
- ES6模块的循环加载
ES6模块与CommonJS有本质区别,ES6模块对导出变量,方法,对象是动态引用,遇到模块加载命令import时不会去执行模块,只是生成一个指向被加载模块的引用,需要开发者保证真正取值时能够取到值,只要引用是存在的,代码就能执行。
案例说明:
//even.js import {odd} from './odd'; var counter = 0; export function even(n){ counter ++; console.log(counter); return n == 0 || odd(n-1); } 复制代码 //odd.js import {even} from './even.js'; export function odd(n){ return n != 0 && even(n-1); } 复制代码 //index.js import * as m from './even.js'; var x = m.even(5); console.log(x); var y = m.even(4); console.log(y);
执行index.js,输出结果如下:
babel-node index.js 1 2 3 false 4 5 6 true
可以看出counter的值是累加的,ES6是动态引用。如果上面的引用改为CommonJS代码,会报错,因为在odd.js里,even.js代码并没有执行。改成CommonJS规范加载的代码为:
//even.js var odd = require('./odd.js'); var counter = 0; module.exports = function even(n){ counter ++; console.log(counter); return n == 0 || odd(n-1); } //odd.js var even = require('./even.js'); module.exports = function odd(n){ return n != 0 && even(n-1); } //index.js var even = require('./even.js'); var x = even(5); console.log(x); var y = even(5); console.log(y);
执行index.js,输出结果如下:
$ babel-node index.js 1 /Users/name/Projects/node/ES6/odd.1.js:6 return n != 0 && even(n - 1); ^ TypeError: even is not a function at odd (/Users/name/Projects/node/ES6/odd.1.js:4:22)