重新理解前端系列 — AMD、CMD
author: @TiffanysBear
本文主要是针对之前一些熟悉的前端概念,再次回顾的时候,结合自己的开发经验和使用,进行再次理解。经过了开发和线上使用之后,会有更为深刻的印象。对比requirejs源码分析,实现一个模块加载器,需要考虑哪些问题。
起源
其实对于AMD和CMD的不同,之前一直是拘泥在使用上的不同。没有深刻的认识为什么会有不同,其实主要是因为浏览器端和 Node 端不同性能特点和瓶颈带来的不同。
早期的js模块化主要用于浏览器端,主要的需求和瓶颈在于带宽,需要将js从服务端下载下来,从而带来的网络性能开销,因此主要是满足对于作用域、按需加载的需求。因此AMD(异步模块定义)的出现,适合浏览器端环境。
而后出现Node之后,主要的性能开销不再是网络性能,磁盘的读写和开销可以忽略不计;CMD的出现更符合Node 对于CommonJS的定义和理解,在运行时进行加载,引入时只是产生引用指向关系。
因此两者产生了不同的使用特点,在出现循环引用时,就产生了不同的现象。以下是针对 requirejs 源码部分的解读。如果有问题,欢迎提问纠正。
1、动态加载一个js模块的方法,怎么保证异步和回调的执行
一先开始是需要判断环境,浏览器环境和webworker环境;如果是浏览器环境,通过document.createElement
创建script标签,使用async属性使js能进行异步加载, IE等不兼容async字段的,通过监听 load 、 onreadystatechange 事件执行回调,监听脚本加载完成。
1 | req.createNode = function (config, moduleName, url) { |
2、怎么判断去加载js,怎么保证加载的顺序
通过 setTimeout 放入下一个队列中,保证加载顺序
1 | //通过setTimeout的方式加载依赖,放入下一个队列,保证加载顺序 |
3、require中的js文件是怎么判断已经loaded,怎么保证加载数据的数量是正确的?
依赖数量,是通过 depCount 来计算的,通过循环遍历,统计具体的依赖数量;
1 | // ... |
判断单个文件加载成功,是通过 checkLoaded 每间隔 50s 做一次轮询进行判断,变量 inCheckLoaded 作为标识;下面是 checkLoaded 函数:
1 | function checkLoaded() { |
4、如果有循环引用,怎么判断出的,怎么解决的
这部分暂且还有点疑惑,先mark一下,之后再理解;
看到有个 breakCycle 函数,执行条件是 needCycleCheck 为 true,但是当 !mod.inited && mod.fetched && map.isDefine
模块未被初始化完成,但是已经获取过定义过之后,且 在 map.prefix 有前缀,会启动 breakCycle 检查;至于为什么要这么做,只能猜测是为了到模块require时循环引用打破轮询查询加载状态等待的问题,现在先留一个疑问。
1 | function breakCycle(mod, traced, processed) { |
但是在CommonJs中时,存在依赖的情况下,因为存在的只是引用,代码执行是在实际调用时才发生,在文件的开头和结尾也会有变量标识是否加载完成。一旦某个模块出现循环依赖加载
,就只输出已经执行到的部分,还未执行的部分不会输出。
在ES6模块加载的循环加载情况下,ES6是动态引用的,不存在缓存值问题,而且模块里面的变量绑定所在的模块;不关心是否发生了循环加载,只是生成一个指向被加载模块的引用,需要开发者自己来保证真正取值的时候能够取到值。