当前位置: 首页 > Web前端 > HTML

深入剖析异步加载和预加载

时间:2023-04-02 14:56:15 HTML

废话:异步加载和预加载一直是前端优化的必备技能之一。今天我们就来深入分析几个常用的关键点。异步加载废话不多说,任何啰嗦的教程都不值得一张清晰明了的高清大图:以及预加载(preload,prefetch,dns-prefetch,preconnect,prerender)")从这张图上,我们看到了什么可以概括为以下四点:默认先解析HTML,再加载JS,此时先中断HTML解析,再执行JS,最后JS执行完毕,恢复HTML解析。defer的情况下,HTML和JS齐头并进,最后执行JSasync情况下,HTML和JS齐头并进,可能在HTML解析之前就完成了JS的执行,最终的模块情况和defer类似,除了提取过程中会加载多个JS文件,基本上已经区分了,我懂了,那我怎么记呢?默认的情况我们已经很熟悉了,就不用再记了。defer的翻译意思是延迟,即pr拖沓,所以是懒惰,也就是说,我什么都不想做,就是你把饭摆在我面前,我也懒得动嘴。这样一想,我们不就记住了,就算你客户端下载了JS文件,我也懒得去执行。到头来,真的是大家都干完了,才舍不得执行JS文件。async翻译过来就是异步,异步,异步的意思,不就是一步一步来吗,一步到位,就是我只要下载了,马上执行,至于其他的,我想都别想。Module翻译过来就是模块的意思。用过es6的人基本都懂这个关键字。加载和defer类似,只是可以加载多个js文件。我们来看看这几个加载DOM的事件时机:和预加载(preload,prefetch,dns-prefetch,preconnect,prerender)")从这张图我们可以看出以下几点:async会在加载完JS后立即执行,最迟会在load事件之前执行,defer会在HTML解析完成后执行,最迟会在DOMContentLoaded事件之前执行。从上面我们可以看出,如果你的脚本依赖DOM是否构建完成,可以使用defer;如果不需要DOM构建,可以放心使用async。deferdefer属性只适用于外部脚本,即只有当src属性存在时才会生效;如果一个script标签上有defer属性和async属性,浏览器会如何解决这种情况呢?我们通过一段代码来验证结果,详情请点击这里。也就是说defer的优先级没有async高。让我们看看规范如何处理这种情况。即使指定了async属性,也可以指定defer属性,以导致仅支持defer(而不是async)的旧版Web浏览器回退到defer行为,而不是默认的阻塞行为。规范只是写明在不支持async的情况下,浏览器会回退支持defer,但并没有明确说明两者都支持的情况,也就是说这种情况是浏览器自己处理的。经过测试,各浏览器表现如下:Chrome浏览器的性能解析为异步特性。Safari浏览器的表现是异步特性。Opera浏览器的表现是异步特性。最高。兼容性我们来看看defer的兼容性。移动端绿色环保,放心使用。IE10以上可以放心使用。IE6-9有个小问题就是不会按照script标签的执行顺序执行。脚本库不用管,但是依赖库就不行了。比如你的项目依赖jQuery,之后马上使用jQuery的方法可能会出现问题。和preloading(preload,prefetch,dns-prefetch,preconnect,prerender)")async和defer一样,只适用于外部脚本,即只有src属性存在时才会生效。兼容性async兼容性在移动端同样是大绿,IE只支持IE10+。和预加载(preload,prefetch,dns-prefetch,preconnect,prerender)")module在现代浏览器中,我们可以声明acript标签type='module'属性来包含ES6的模块导入和导出语法,像这样:看起来不像人很兴奋,貌似对开发者也很友好,但是和传统脚本也有几点不同:模块默认使用“usestrict”模式,也就是不能使用arguments.callee等语法,模块会只加载一次,不管前后写多少次。不支持注解。模块有自己的词法作用域,比如定义一个vara=1,并且没有创建全局变量,所以不能通过window.a访问它的值。模块导入方式目前只支持以下几种模式:supportimport{math}from'./math.mjs';import{math}from'../math.mjs';import{math}from'/modules/math.mjs';import{math}from'https://simple.example/modules/math.mjs';//不支持从“jquery”导入{math};当然,浏览器厂商也在考虑支持import{math}from'jquery'这种格式,但是还有很长的路要走。module默认是defer,所以不需要在module中加入defer很熟悉,不支持这种写法,但是支持async属性,其加载和渲染方式类似async,这里就不细说了。移动端兼容性还算不错,但是IE好像败下阵来了。加油,只要还是支持edge16+,对于不支持modules的浏览器,可以使用nomodule属性作为版本回退方案。以及预加载(preload,prefetch,dns-prefetch,preconnect,prerender)")最后说一下模块的使用。对于大型项目(超过100个模块)不建议直接使用模块语法。你应该使用Webpack、Rollup、Parcel等打包工具,由于静态导入或导出语法是静态可解析的,可以通过打包工具去除冗余模块,我们考虑如下场景:import{Modal}from'./util.js';Modal({title:'hello'})如果我们用打包工具打包这段代码,最终生成的js文件只会包含Modal函数,如果不使用打包工具,浏览器会下载整个utiljs文件,并且通过进一步分析,我们了解到使用了Modal函数,对于没有使用util中所有函数的方法来说,这是一种不必要的带宽浪费。预加载是我们浏览器加载资源的时候。对于每个资源都有它们自己的默认优先级,如果我们能够修改每个资源的默认优先级,我们几乎可以按照我们的预期加载我们想要加载的资源。以谷歌浏览器为例,我们打开控制台,切换到网络选项,点击刷新页面,在网络下的标题行点击鼠标右键,勾选优先级,可以看到加载资源的优先级。我们可以看到样式级别高于脚本优先级。毕竟页面Loadingin的一部分肯定是需要先渲染的样式,否则整个页面就会被撕裂,用户体验不好。而preloading(preload,prefetch,dns-prefetch,preconnect,prerender)")preloadpreload翻译为预加载。一旦启用,它会通知浏览器应该尽快加载资源。如果提取的资源不可用3s使用,在GoogleDevTools中会触发警告信息和preload(preload,prefetch,dns-prefetch,preconnect,prerender)")大概语法如下:除了上面指定的资源外,还可以加载音频、字体、视频和文档。有关详细信息,请单击此处。跨域资源如果需要加载跨域资源列表,需要正确设置CORS,然后可以在元素中设置crossorigin属性:这里有个特例,不管是否跨域,字体获取都需要设置crossorigin属性。这是由于历史原因。有兴趣的可以移步这里了解,另外,我们还可以使用media来响应式加载图片,比如:另一个重点是如果一个脚本预加载,不执行://只拉取下载不执行varpreloadLink=document.createElement("link");preloadLink.href="foo.js";preloadLink.rel="preload";preloadLink.as=“脚本”;文档.head。appendChild(preloadLink);//如果需要执行varpreloadedScript=document.createElement("script");preloadedScript.src="foo.js";document.body.appendChild(preloadedScript);兼容性IE好像全杀了,edge得17+才能勉强支持。Firefox需要手动启动才能支持。移动端支持还是比较不错的。而预加载(preload,prefetch,dns-prefetch,preconnect,prerender)")预取的简称,预取就是在我们的页面加载后带宽可用的时候加载用户接下来期望的页面资源,比如企业认证一般是分为几个页面进行认证,当用户从第一个页面进行认证时,页面加载完成,用户填写表单数据后,加载第二个页面的一些资源,以便用户更快打开下一个页面,从而增加用户体验,例如:当浏览器解析到link标签时,读取值rel为prefetch,并将该资源加入到队列中,当浏览器空闲时,会预取该资源,但demo.html页面中只加载HTML,不会加载demo页面中的任何其他资源,除非你在demo页面明确使用prefetch,主流浏览器都支持兼容,IE11+及以上,不过Safari好像到现在才支持。我们都知道dns-prefetch,当我们在浏览器地址栏输入域名时,首先要做的就是域名解析,因为我们需要加载域名对应的资源。这个过程很快,但是如果是在移动端,就是一个分秒必争的地方。当一个页面需要访问很多外部域名的资源时,如果我们能够在用户浏览页面的时候和浏览器空闲的时候提前解析出可能需要访问的域名,是不是会大大增加数量用户打开?页面的响应时间增加了用户体验。为了解决这个问题,w3c提出了一个标准,学名是dns-prefetch。而preloading(preload,prefetch,dns-prefetch,preconnect,prerender)")的使用方法上面已经支持了,指定rel="dns-prefetch",在href中指定页面需要解析的域名。你可能注意到上图中的域名使用了双斜杠,这个双斜杠表示URL以主机名开头,相当于使用了完整的URL(比如http://g.alicdn.com/).RFC1808中指定。当然,并不是所有页面需要用到的外部域名都需要做这样的域名解析。浏览器默认会解析超链接属性的href中的域名,你的网站域名不能是HTTPS。如果是HTTPS,则需要设置请求头或者添加强制开启域名解析的meta标签。//HTTP//过度//HTTPS//强制开启当然对于HTTPS网站不建议开启强制解析,因为这样会带来一些安全隐患,请参考这里。preconnect预连接,即启动earlyconnection(包括DNSlookup、TCPhandshake和可选的TLSnegotiation),我们看一个例子:网页字体的正常加载一般包括:页面加载样式,解析样式时使用的网页字体开始下载网页字体,先开始DNS查找,然后是TCP握手如果是HTTPS,还有TLS协商,最后下载字体。一个字体的渲染要经历几个过程,但是如果字体准备(DNS查找,TCP握手和可选的TLS协商)和样式加载并行执行,页面渲染的速度是否更快,preconnect就是为此而生的,所以以优化用户体验。当然,如果是跨域资源,别忘了加上crossorigin属性。兼容性IE15+及以上部分兼容,移动端良好兼容。而preloading(preload,prefetch,dns-prefetch,preconnect,prerender)")prerender预渲染,简单的说就是浏览器会下载指定链接的资源,并下载并渲染,就像我们打开一个新的Tab一样标签页是在后台静默下载执行的,当然浏览器不一定会下载渲染,这取决于很多条件,比如浏览器是否空闲,操作系统是否会放弃下载资源文件太慢了。除非你真的非常确定用户接下来会触发你指定的资源地址,否则对用户来说就是浪费带宽。使用示例如下:Compatibility虽然prerender是HTML5规范的一部分,但好像很多厂商还没有实现,不过IE11是支持的。以及预加载(preload,prefetch,dns-prefetch,preconnect,prerender)")说到最后,最后说了这么多,最后整理了一张表格,帮助大家快速参考。各个浏览器的实现细节是不同。这里以Chrome浏览器表为例:和preload(preload,prefetch,dns-prefetch,preconnect,prerender)")参考:[1]https://www.w3.org/TR/resource-hints/#prerender[2]https://dev.chromium.org/developers/design-documents/dns-prefetching[3]资源优先级–让浏览器为您完成工作[4]Chrome中的JavaScript加载优先级[5]Chrome资源优先级和调度[6]在网络上使用JavaScript模块[7]https://www.w3.org/TR/html5/webappapis.html#module-script原文出处:异步加载(defer、async、module)和预加载深入分析(预加载、预取、dns预取、预连接、预渲染)