很多时候看到这些名词的时候都有一种傻傻分不清楚的感觉,今天就来缕缕。

commonjs

nodejs中的模块化机制,模块通过require()引入,exports或modules.exports导出。

AMD

上面的commonjs主要适用于node.js这种后端模块化,其中的require引用是同步的,前端环境中有很多异步的情况,因此commonjs是不够用的,AMD就出现了。

RequireJS用的就是AMD,AMD用define定义方法,用require引用模块。

CMD

CMD规范是阿里的玉伯提出来的,实现js库为sea.js。和requirejs非常相似,也是前端异步加载模块化。不同的是它的模块是需要的时候去请求,而不是先加载再使用。

AMD 是提前执行,CMD 是推迟执行;AMD推崇依赖前置,CMD 推崇依赖就近。

ES6 modules

在script的标签上加上type='module'即可使用es6 模块化功能。用import引入,export或export default导出。

ES6 module的加载是基于defer的,我们来看看ES6 module的加载过程:

首先我们的模块引用关系如下(main.js中引用了a.js,a.js中引用了b.js):

ES6 module存在一个静态编译过程,这个过程只关心页面上的import命令,识别页面上的import,然后去加载那个js,加载完成后,再去识别页面上的import,继续加载。。。

commonjs,AMD,CMD和ES6 modules gif commonjs,AMD,CMD和ES6 modules

我们可以发现,js根本都还没有执行完,但是却早早的加载完成了,这就是静态编译导致的。

同时它的运行和编译的方向是相反的, 因为它要先执行import的模块,拿到引用,然后赋值回来;commonjs则是先运行当前模块,遇到了require再去下一个模块,执行完了再回来:

background Layer 1 import {b} from ('./b.js'); console.log('bbb'); export let b=‘name' 模块a 模块b 先执行模块b,再输出'aaa','bbb' console.log('aaa'); ES6 module引用 let b=require('./b.js'); console.log('bbb'); exports.name='张三' 模块a 模块b 先执行'aaa',遇到require,去执行模块b,执行完成返回模块a,输入'bbb' console.log('aaa'); commonjs引用

正是因为编译和运行是反的,所以导致我们引用的顺序为main.js->a.js->b.js,但实际输出顺序是b.js->a.js->main.js。

上面的demo中,我们给模块a和模块b都设置了很耗时的操作,但是最后我们都是在DOMContentLoaded事件之前执行完成,说明ES6 module中模块的加载都是基于defer的。

作为对比,我们可以看看commonjs中模块的加载(commonjs本来是node.js里面用的,这里用了个简单的模拟器,让网页上也可以使用commonjs规范。):

我们可以发现commonjs的模块加载都是在DOMContentLoad之后的,同时路线是迂回的。

ES6 module和commonjs的引用问题

ES 6module中传递的是引用,commonjs中传递的是数值的拷贝。

我们先来看ES6 module中的:

上面这种导出的模块,当外面修改后,内外都能得到正确的答案,说明导出的是引用,而不是具体的值。

但是上面这种情况并非在ES6 module的所有场合都成立,我们修改代码如下:

我们可以发现,如果导出的是对象的话,调用addCount,自加的count其实是原模块的,外部模块的count并没有变化。

所以,导出的时候带了定义的,外部模块可以获取到,例如export let ***或者export{aaa,bbb,**}等,如果定义在原模块中,外部模块就无法获取正确的值。

commonjs中传递的是数值的拷贝:

这种和上面的情况类似,因为count是在原模块上定义的,变化后,外部模块得不到正确的值。

如果想让外部得到正确的值,我们可以将内部的count返回,这样就可以保证内外得到的count是一致的:

但是commonjs无法像ES6 module那样导出带定义的变量,commonjs的定义都是在原模块内部。

ES6 module和commonjs的循环引用

弄懂模块的循环引用,可以更深入的理解模块引用规则。

我们先来看看ES6 module的循环引用:

如果只是a引用b,那么b会先执行,然后a再执行,可是这里b又引用a了,这该怎么执行呢?

上面说到,import会先执行引用模块,拿到引用,然后赋值,因此这里是先执行a,然后执行b,最后执行a。

那么a模块第一次执行的时候,为啥可以拿到b3呢?之前说过ES6 module有个静态编译过程,在静态编译过程中,如果遇到export function(){}的,那么会提升。而b3刚好符合条件,因此可以得到。

但是a模块第一次执行的时候,b1,b2无法拿到,如果你输出b1,b2会报错。这也说明静态编译的时候不会去管import *** from **** 前面的部分,会默认你import的都存在,运行时如果发现不存在的,程序就会报错。

commonjs的循环引用:

a模块执行时,只是得到了部分输出(done=false),被缓存起来了,执行require('js/b.js')跳转到b模块,在b.js中查看a模块输出就是刚才的部分输出。

commonjs中的所有模块输出都是统一存储的,所有模块输出只会被创建一次,下次就是拿缓存的输出了。

其他文章

0
我要评论

评论

返回
×

我要评论

回复:

昵称:(昵称不超过20个字)

图片:

提交
还可以输入500个字