模块加载
模块加载
1.web浏览器中使用模块
es6之前(脚本加载):
1.在script元素中通过src属性指定一个加载代码的地址来加载js文件
2.将js代码内嵌到没有src属性的script元素中
3.通过web worker 或者Service Worker方法加载执行js文件
<!-- 页面内嵌的脚本 -->
<script type="application/javascript">
// module code
</script>
<!-- 外部脚本 -->
<script type="application/javascript" src="path/to/myModule.js">
</script>
在script中,type='module'时,支持加载模块;将type设置为module可以让浏览器将所有内联代码或包含在src指定的文件中的代码按照模块而非脚本的方式加载。
如demo
<!--加载一个js模块文件-->
<script type='module' src='module.js'></script>
<!--内联引入一个模块,嵌入在网页中的模块-->
<script type='module'>
import { sum } from '../example.js'
let result = sum(1,2)
</script>
// 变量result没有暴露到全局作用域,只是存在于模块中
// 浏览器对于带有type="module"的<script>,都是异步加载,不会造成堵塞浏览器,即等到整个页面渲染完,再执行模块脚本,等同于打开了<script>标签的defer属性
什么是defer属性?defer要等到整个页面在内存中正常渲染结束(DOM 结构完全生成,以及其他脚本执行完成),才会执行
延伸: 什么是async属性? async一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染。
defer:渲染完再执行 (如果有多个defer脚本,会按照它们在页面出现的顺序加载)
async:下载完就执行 (而多个async脚本是不能保证加载顺序的)
2. web浏览器中模块加载顺序
<!--先执行这个标签-->
<script type='module' src='module1.js'></script>
<!--再执行这个标签-->
<script type='module'>
import { sum } from '../example.js'
let result = sum(1,2)
</script>
<!--最后执行这个标签-->
<script type='module' src='module2.js'></script>
注意:如果网页有多个<script type="module">,它们会按照在页面出现的顺序依次执行
**加载脚本文件时,defer是可选属性;
**加载模块时,defer是必须属性
总之,每个模块都可以从一个或多个其他的模块导入,虽然会使得问题复杂化,首先解析模块以识别所有导入的语句;然后每个导入语句都会触发一次获取过程(网络或缓存),
并且在所有导入资源都被加载和执行后才会执行当前模块。
我们再来看,用script type='module' 显示引入和用import隐式导入所有模块都是按需加载并执行的。分析上述加载顺序看看
1. 下载并解析module1.js
2. 递归下载并解析module1.js中导入的资源
3. 解析内联模块
4. 递归下载并解析内联模块中导入的资源
5. 下载并解析module2.js
6. 递归下载并解析module2.js中导入的资源
加载完成后,只有当文档完全被解析(dom渲染完)之后才会执行其他操作。而当dom渲染完之后,发生的动作:
1.递归执行module1.js中导入的资源
2.执行module1.js
3.递归执行内联模块中导入的资源
4.执行内联模块
5.递归执行module2.js中导入的资源
6.执行module2.js
3. web浏览器异步模块加载
<script>标签的async属性也可以打开,这时只要加载完成,渲染引擎就会中断渲染立即执行。执行完成后,再恢复渲染
<script type="module" src="./foo1.js" async></script>
<script type="module" src="./foo2.js" async></script>
注意:无法保证这两个哪个先执行
一旦使用了async属性,<script type="module">就不会按照在页面出现的顺序执行,而是只要该模块加载完成(包含所有导入的资源),就执行该模块
ecmacript 6中:http://es6.ruanyifeng.com/#docs/module-loader
4.将模块作为worker加载
web worker
Service worker
可以在网页上下文之外执行js代码。创建一个worker实例
// 按照脚本的方式加载script.js
// @params: 传入js文件的地址
// 默认的加载机制是按照脚本的方式加载文件
let worker = new Worker('script.js')
// 按照模块的方式加载module.js
let worker = new Worker('module.js', {type: 'module'})
给第二个参数传入一个对象,type属性为 module
使用worker要注意:
1.worker脚本只能从与引用的网页相同的源加载,worker模块不会完全受限
2.worker模块具有相同的默认限制,但是他们还是可以加载并访问具有适当的跨域资源共享cors头的文件
3.worker脚本可以使用self.importScripts()方法将其他脚本加载到worker中;而worker模块使用的是import进行导入
5. 浏览器模块说明符解析
模块说明符 module specifier 使用的都是相对路径 ('./index.js')
浏览器要求模块说明符具有的几种格式:
1. 以 / 开头的解析为根目录开始
2. 以./开头的解析为从当前目录开始
3. 以../开头的解析为从父目录开始
4. URL格式