JS的异步加载
异步加载
先看一张整体的异步加载对渲染的阻塞情况图,图片如下:
从这张图里我们可以看到如下4点:
- 默认情况HTML解析,然后加载JS,此时HTML解析中断,然后执行JS,最后JS执行完成恢复HTML解析
- defer情况下HTML和JS并驾齐驱,最后才执行JS
- async情况则HTML和JS并驾齐驱,JS的执行可能在HTML解析之前就已经完成了
- 最后module情况和defer的情况类似,只不过会在提取的过程中加载多个JS文件罢了
然后我们再来看一下这几种加载JS的情形与DOM事件、onload事件的关系:
从上面的图片我们可以看到如下几点:
- async 会在加载完JS后立即执行,最迟也会在load事件前执行完。
- defer会在HTML解析完成后执行,最迟也会在DOMContentLoaded事件前执行完。
从上面我们可以看出,如果你的脚本依赖于DOM构建完成是否完成,则可以使用defer;如果无需DOM的构建,那就可以放心的使用async了。
defer
defer属性仅适用于外部脚本,也就是仅当存在src属性时才会生效;如果一个script标签上面即存在defer属性,也存在async属性,那么浏览器会如何解析这种情况呢?我们通过一段代码验证结果,详情点击这里。
也就是说defer的优先级没有async高,我们看一下规范是怎么处理这种情况的。规范只是说明了在不支持async的情况下浏览器将会回退支持defer,但并没有明确指明两种都支持的这种情况,也就是说这一种情况浏览器自行处理,经过测试,各个浏览器表现行为:
- Chrome浏览器表现为解析为async特性
- Safari浏览器表现为async特性
- Opera浏览器表现为async特性
- Firefox浏览器表现为async特性
IE暂时没有安装,看来各大浏览器表现一致,总之async的优先级是最高的。
defer兼容性
下面来看看defer的兼容性,移动端一片大绿,可以放心使用,IE10以上可以放心使用,IE6-9有一点小问题就是不会按照script标签的执行顺序进行执行,对于不依赖前后脚本库的可以不用担心,但是如果依赖库的就不行了,比如你的项目依赖jQuery,后面紧接着使用jQuery的方法可能就会出现问题。
asyc
和defer一样,也仅仅适用于外部脚本,也就是仅当存在src属性时才会生效。
async兼容性
async的兼容性在移动端也是一片大绿,IE仅支持IE10+。
module
在现代浏览器中,我们可以声明acript标签type=’module’属性从而拥抱es6的模块导入导出语法,就像这样:
1 <script type="module"> 2 import { Max } from "./math.js"; 3 console.log(Max(1, 2, 7, 2, 0)); //7 4 </script>
看起来是不是令人很激动,似乎对于开发者十分友好,但是这里也有几个与传统脚本不一样的地方:
- module默认使用了”use strict”模式,这也意味着不能使用诸如arguments.callee这一类的语法。
- 模块只会加载一次,无论前后你写了多少次
- 不支持<!–const a = 1–>注释。
- module有自己的词法作用域,比如定义一个 var a = 1,并不会创建一个全局变量,因此你并不能通过window.a 访问到它的值
模块的导入方式目前仅支持以下几种模式:
1 支持 2 import {math} from './math.mjs'; 3 import {math} from '../math.mjs'; 4 import {math} from '/modules/math.mjs'; 5 import {math} from 'https://simple.example/modules/math.mjs'; 6 //不支持 7 import {math} from 'jquery';
当然,浏览器厂商也在考虑支持 import {math} from ‘jquery’ 这种格式,不过,还是需要一段很长的路要走。
module的默认情况就是defer的,因此不必再module上面又添加一个defer熟悉,并且本身就不支持这种写法,但是支持async属性,其加载渲染方式和async差不多,这里不再赘述。
module兼容性
在移动端的兼容性还算可以,但是IE貌似都败下阵来,只要edge16+以上还算支持,对于不支持module的浏览器可以使用nomodule属性作为版本回退的方案解决。
最后来说一下module的使用建议,大型项目(100模块以上)不建议直接使用模块语法,应该使用打包工具诸如Webpack,Rollup,、或 Parcel,因为静态导入或导出语法是静态可分析的,通过捆绑工具可以去掉多余的模块,我们考虑下面这一种场景:
1 import { Modal } from './util.js'; 2 Modal({ 3 title: 'hello' 4 })
如果我们通过打包工具打包这一份代码,最终生成的JS文件将会只包含Modal这一个函数,倘若我们没有使用打包工具,浏览器将会下载整个util这一个JS文件,并通过进一步分析了解了使用了Modal这一个函数,这对于没有用到util里面的全部函数的方式,则是一种多余的带宽浪费。