JavaScript模块加载器的原理

1.加载的js路径问题
其中’a’、’b’是ModuleId。通过id和路径的对应原则,加载器才能知道需要加载的js路径。
一般路径是:baseUrl + id(‘a.js’|b.js).
但是ID和path的对应关系并不是永远那么简单,比如在AMD规范里就可以通过配置paths来给特定的ID指配path。
2.如何加载指定路径的js?
知道js路径之后,需要去请求js,一般是通过createElement(‘script’) && appendChild去请求,有的时候加载器也通过AJAX去请求脚步内容.
一般来说,需要给script标签设置一个属性用来标识模块ID(如ent.ifeng.com,使用requirejs),下面会提到这个的作用。
27f4457d8bc0c8afef77649806551032
ent.ifeng.com

3.匿名模块加载的js,如何知道其定义的是哪个模块呢?
知道路径之后,请求了此路径之后,加载到的js,格式一般如下:

define(id,factory);
//或者:
define(factory);

后者被称为匿名模块,那么当匿名模块被执行的时候,我们怎么知道当前被定义的是哪个模块呢?
具体的说,这个匿名模块的实际模块ID是什么?答案是通过document.currentScript获取当前执行的

<script>

,然后通过上面给script设置的属性来得到模块ID。
需要注意的是,低级浏览器是不支持currentScript的,这里需要进行浏览器兼容。在高级浏览器里,可以通过script.onload来处理这个事情。
4.如何进行依赖分析?
在继续讲之前,需要先简单介绍下模块的生命周期。模块在被define之后并不是马上就可以用了,在你执行他的factory方法来生产出最终的export之前,你需要保证它的依赖是可用的。那么首先就要先把依赖分析出来。
简单来说,就是通过toString这个方法得到factory的内容,然后用正则去匹配其中的require(‘moduleId’).当然也可以不用正则。
这就是为什么require(var);这种带变量的语句是不被推荐的,因为它会影响依赖分析。如果一定要用变量,可以用require([var])这种异步加载的方式。
5.递归加载
在分析出模块的依赖之后,我们需要递归去加载依赖模块。用伪代码表示大概是这样的:

modeule.prototype.load = function(){
	var deps = this.getDeps();
	for(var i = 0; i<deps.length; i++){
		var m = dep[i];
		if(m.state < STATUS.LOADED){
			m.load();
		}
	}
	this.state = STATUS.LOADED;
}

上面的代码只是表达一个意思,实际上load方法很可能是异步的,所以递归的返回要特殊处理下。
实际一个可用的加载器并没有那么简单,比如你要处理循环依赖,还有各种各样的牵一发懂全身的细节。
注:
模块和加载是两个部分.
模块是说你怎么组织你的代码,针对的是开发者。加载是说代码怎么跑到浏览器去,针对的是机器,是运行时。一开始这两个部分是合在一起的,也就是所谓的seajs requirejs。优点是按需加载,具体没实现过 不过就是类似于添加script标签加载js文件,同时也要对依赖等问题进行管理。后来出现了一些工具如browserify gulp等打包的工具,真正实现了把这个过程分开。首先把代码按照那些模块系统(决定生态环境)打包生成为一个源码包,然后直接通过浏览器原生支持的script link等直接引用就好。这方案的优势在于可以在运行代码前,把代码优化、压缩以及依赖等问题都给解决掉,更适合工程化。于是,两种方案各有优劣,所以,我预测下一个解决方案是能把以上组合在一起使用的 那就是es2015 module。

来源:知乎
参考文章:
JS模块加载器加载原理是怎么样的?
JS模块加载器加载原理是怎么样的?

此条目发表在JavaScript分类目录。将固定链接加入收藏夹。

发表评论

电子邮件地址不会被公开。 必填项已用*标注