You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
// a.jsmodule.exports={a(){console.log('this is module a, function a');},aa(){console.log('this is module a, functon aa');}};// b.jslet{a, aa}=require('./a');// 等同于letA=require('./a');leta=A.a;letaa=A.aa;
一直以来对 CommonJS 和 ES6 的模块机制的认识很模糊。此文来梳理一下二者的用法和区别。
静态编译 VS 运行时加载
CommonJS
CommonJS是在运行时确定模块的依赖关系。举🌰:
CommonJS 实质上就是加载整个模块,生成一个对象 A。再从对象上读取方法。这种就是“运行时加载”。只有在运行时才能得到这个对象,导致没办法在编译时做“静态优化”。
ES6
ES6 的模块思想是尽量的静态化,编译时就能确定模块的依赖关系。举🌰:
上面代码就直接加载 a 和 aa 方法,其他方法不加载。这种就称为“静态加载”,在编译时就完成模块加载。
基本语法
CommonJS
Node 内部提供一个 Module 构建函数。所有模块都是 Module 的实例。
上面例子直接输出当前模块的 module,可以查看到:
将注意力看到
parent: null
。在命令行下返回是 null,可以用于判断模块是不是入口。module.exports
表示当前模块对外输出的接口,其他文件加载该模块,实际上就是读取module.exports变量。
exports
为了方便,Node为每个模块提供一个exports变量,指向module.exports。这等同在每个模块头部,有一行这样的命令:
造成的结果是可以直接向 exports 挂一些属性:
特别小心的是,不能直接将一个变量或方法赋值给 exports:
还有一种写法也要特别小心:
从上面代码中可以得出:
require
require命令的基本功能是读入并执行一个JavaScript文件,然后返回该模块的exports对象。举个🌰:
加载规则
针对规则的第6条举个🌰:
模块缓存
在第一次加载某个模块后,Node会缓存该模块。以后再加载该模块,就直接从缓存取出该模块的module.exports属性。举🌰:
删除缓存也很简单:
循环加载
上面代码中, a 加载 b , b 加载 a ,在 main 中引用两个文件,输入如下:
将 main 中的代码改成如下:
猜猜输出是什么?
第一次加载后,会缓存模块。第二次再加载 a.js 和 b.js ,直接从缓存读取 exports 属性,所以最前面的两条输出都没有了。
模块的加载机制
CommonJS模块的加载机制是,输入的是被输出的值的拷贝。也就是说,一旦输出一个值,模块内部的变化就影响不到这个值。
一个通用的🌰:
要解决上述问题可以将 counter 属性换成取值函数:
这样再执行 main.js,会得到想象中的值:
ES6 module
export
export命令用于规定模块的对外接口。
需要特别注意的是:
上面代码输出变量 foo,值为 bar,500 毫秒之后变成 baz。
import
import 命令用于输入其他模块提供的功能。同样的, import 也可以用关键字 as 重新命名。建议不要修改 import 进来的变量或方法。
上述代码能够正常运行的本质是 import 命令是编译阶段执行的,在代码运行之前。
export default
上面代码中,可以用任意名字指向 a.js 中的函数。需要注意的是,此时名称没有大括号。
本质上,export default 就是输出一个叫做 default 的变量或方法,然后系统允许你为它取任意名字。因为输出的是叫 default 的变量或方法,所以 export default 后面不能接变量声明语句。
import()
前面提到过,import 是静态编译的,无法做到动态加载。import() 就是解决 ES6 模块不能运行时加载问题的。同 CommonJS 的 require。不同在于 require 是同步加载的,import() 是异步加载。
import() 的作用
import() 可以在需要的时候,再加载某个模块。
import()可以放在if代码块,根据不同的情况,加载不同的模块。
动态加载
上述代码在 CommonJS 中也出现过,不同点是第二次输出 counter ,ES6 模块输入的变量是活的,完全反应在其模块中的变化。再看一个🌰:
查看结果:
上面代码表明,ES6 模块不会缓存运行结果,而是动态地去被加载的模块取值,并且变量总是绑定其所在的模块。
最后,export通过接口,输出的是同一个值。不同的脚本加载这个接口,得到的都是同样的实例。
循环加载
ES6 处理“循环加载”与 CommonJS 有本质的不同。ES6 模块是动态引用,如果使用import从一个模块加载变量,那些变量不会被缓存,而是成为一个指向被加载模块的引用。看🌰:
上面代码中,a.mjs加载b.mjs,b.mjs又加载a.mjs,构成循环加载。执行a.mjs,结果如下:
顺一下 ES6 循环加载处理机制:
解决方法:
结果是:
ES6 模块加载 CommonJS 模块
CommonJS 模块输出都在
module.exports
上。Node 的 import 命令加载 CommonJS 模块,Node 会自动将module.exports
属性,当作模块的默认输出,即等同于export default xxx
。通过 import 获取上面的模块:
CommonJS 模块的输出缓存机制,在 ES6 加载方式下依然有效。
上面代码中,对于加载 foo.js 的脚本,module.exports 将一直是123,而不会变成 null 。判断下面写法是否正确:
上述代码是错误的,因为 fs 是 CommonJS 格式。只有运行时才能确定 readFile 接口,而 import 需要在编译时就确定接口。改为整体输入的方式:
CommonJS 模块加载 ES6 模块
CommonJS 模块加载 ES6 模块,不能使用require命令,而要使用 import() 函数。
The text was updated successfully, but these errors were encountered: