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

资源预加载-性能优化须知~

时间:2023-04-02 19:19:12 HTML

首发地址:https://mp.weixin.qq.com/s/8_...本文主要介绍前端性能优化中的资源预加载,不仅是常规的一些预加载方法;还介绍了在工程实践中的应用。涉及内容:链接相关(rel、as、media、defer、async);缓存(4种缓存、缓存策略、ServiceWork);优化网络(HTTP/2ServerPush、Preload/Prefetch、域名拆分);同步接口JSON数据Inline,加速首页渲染;浏览器中资源加载的优先级;实践:webpack插件、quiklink。前言当我们需要某些网络资源时,加载和执行往往是耦合在一起的,下载后立即执行,而加载过程是阻塞的,会延长onload时间。因此,如何在资源执行前预加载资源,减少等待网络的开销,是我们要讨论的问题。1.常规做法附上不同资源浏览器优先级的示意图(来源):1)async/defer:非阻塞加载defer:在DOMContentLoaded事件触发前执行;实际上,延迟脚本不一定按顺序执行,不一定在DOMContentLoaded事件触发前执行,所以最好只包含一个延迟脚本;async:加载后立即执行,无法控制执行时机和执行顺序。适用于没有依赖关系的外部独立资源。缺点:限于脚本资源;执行时机不可控或执行顺序有问题,用于非关键资源。2)使用ajax加载资源:可以实现预加载。Insufficient:优先级低,无法提前加载首屏资源。3)Webkit浏览器预测分析:chrome的预加载扫描器html-preload-scanner扫描节点中的“src”、“link”等属性,找到外部连接资源并预加载,避免了资源加载的等待时间,也实现了提前加载,加载和执行分离。原始解析方法:使用预解析器(扫描器)设备:不足:-仅解析HTML中收集的外部链接资源,无法预先收集JS异步加载的资源。-不暴露类似Preload的onload事件。4)服务器推送图片源资源推送:链接:;rel=预加载;作为=样式;资源只预加载,不推送:链接:;rel=预加载;作为=样式;nopush目标:减少请求数,提高页面加载速度。特点:多页面共享推送缓存(动态数据json除外)适用场景:如果没有推送该资源,浏览器就会请求该资源。需要注意:确保没有发起不必要的推送,浪费流量。您可以改用preload标记,或将nopush属性添加到HTTP标头。Tips:如果服务器或浏览器不支持HTTP/2,浏览器会根据preload处理header信息,预加载指定的资源文件。缺点:-对Edge和Safari的支持不好,谨慎使用;-如果浏览器不从推送缓存中获取资源,推送不利于网页性能;-只能推送没有请求体的GET和HEAD请求;-pushcache中的资源只能使用一次;-无论客户端缓存如何,始终推送:-只对第一次访问的用户启用服务器推送;效果更好;-更新资源文件的缓存状态到客户端的Cookie。-只能推送同源资源;-push缓存:只存在于session中,session结束后释放;-即使推送的是最新的资源,如果http缓存中的max-age还没有过期,仍然会在http缓存H中使用。(资源依次查找缓存的顺序:Memorycache,ServiceWorkercache,Diskcache,Pushcache)-无加载/错误事件[问题]不仅js渲染被阻塞,有时需要等待js执行并获取一些数据(JSON)才能真正渲染完成,如何解决?(参考图片来源)1)提前推送动态资源,注意参数需要固定,不能随机变量;webpack异步模块加载里面有类似__webpack_require__.e的实现,感兴趣的童鞋看这里。2)服务器端渲染(同构直出)比如在渲染节点层模板时,首先缓存从同步接口拉取的数据同步数据,比如挂载在window._data上,使用时直接从全局变量中读取.好了,上面介绍了一些常用的预加载方式,下面就来介绍一下主角~preload和prefetch的概念preloaddeclarativefetch可以强制浏览器去请求资源,不会阻塞文档onload事件。使用场景:当前页面被使用(一般用于与首屏渲染无关的逻辑,如数据管理等),尽快下载,优先级较高;第一次渲染不需要预取,但以后可能需要。它的优先级较低,只有在浏览器空闲时才会下载。使用场景:比如当前页面可能跳转到的页面,或者条件加载的资源。特点——预加载的资源存储在内存缓存中(未设置资源缓存策略时);-下载但不执行;-异步加载,不影响当前页面的渲染;-预加载资源,实际使用时,直接从缓存中读取;-使用场景-在分析当前页面用户经常点击的链接时,分析提取跳转页面上的资源,使用prefetch进行预加载。-font字体文件的预加载会导致页面的字体样式闪烁,因为字体文件必须等到CSSOM构建好并应用到页面元素后才加载。为了避免FOUT,浏览器会尝试等待字体加载完成,然后再显示应用了字体的内容,这会导致加载完成前显示一片空白。检测prelaod和prefetch的支持let{relList}=document.createElement('link');返回relList&&relList.supports&&relList.supports('preload');如何使用//link标签//动态创建letpreLink=document.createElement('link');preLink.rel='预取';//感觉动态创建不适合preloadprelink.as='script';preLink.href='./a.js';as属性的不同值表示资源类型,对应不同的优先级:style、script、image、media、document、font。(问题:官方说法:不带as属性的preload优先级会等同于异步请求,经测试发现不写as是不会发送请求的。)注意事项-造成二次下载-使用as=对于同一个资源单独预加载'style'和as='script'会导致二次下载;-同时对同一个资源使用prefetch和preload,导致二次下载;-其实是script脚本,但是使用as='style'会造成二次下载二次下载;-预加载不带crossOrigin的字体时(默认指定anonymous,不带认证信息),即使同域,也会导致第二次预加载字体时包含crossOrigin,否则也会造成二次下载;没有凭据的请求使用单独的连接。-如果没有使用preload资源,Chrome会在onload事件后3s发出警告,避免无效优化和流量浪费。-浏览器对同一个域名的并行加载数量有限制,所以要考虑域名拆分等优化。以下是twitter页面中的应用://head//bodybottom预加载polyfill背景知识//如果支持preload,异步下载后不会立即执行。//下载后立即应用于DOM树。//异步下载,只有打印的时候才会应用,如果不匹配就不应用,所以不会阻塞渲染。polyfill实现思路见loadCSS,提供了csspreload的polyfill实现(只显示关键代码)://1.【支持preload】//因为preload是只是获取样式,不会立即应用,所以使用onload改变链接的rel,使其立即生效9)注意:设置onload=null主要是因为部分浏览器会在rel变化时再次触发load事件。//2.【不支持preload的情况:核心还在使用media】//1)获取所有linkletlinks=document.getElementsByTagName("link");//2)缓存mediavarfinalMedia=link.mediafor每个链接||"all";//3)改变链接的rel和media(异步下载但未应用)link.rel="stylesheet";link.media="onlyx";//4)如果绑定了onload事件(为了启用媒体)if(link.addEventListener){link.addEventListener("load",enableStylesheet);}elseif(link.attachEvent){link.attachEvent("onload",enableStylesheet);}//5)处理老浏览器不支持链接的onload事件。setTimeout(enableStylesheet,3000);//6)在enableStylesheet回调中做如下操作:恢复媒体,立即应用样式link.setAttribute("onload",null);link.media=finalMedia;注意这里实现的是CSS的预加载。适用于与首页无关的样式:由于preload可以异步加载样式,因此可以避免在加载与首页无关的样式时阻塞初始渲染。对于首页初始渲染中的重要样式:1)Inline(注意静态资源的缓存策略会和页面的缓存策略捆绑在一起)2)HTTP/2serverPush》知道preload和prefetch的目的,如何结合项目实践呢?由于webpack基本是项目必备,我们先介绍webpack的使用;然后简单介绍一下quiklink。》结合webpack实践1.使用插件:PreloadWebpackPlugin常用配置如下:newPreloadWebpackPlugin({//预加载或预取方法rel:'',/**是中的as,表示资源类型,不同的类型决定不同的执行优先级*例如:script的优先级大于style*/as:'',//被排除的html页面的集合,即只与待关联的页面关联configuredexcludeHtmlNames:[],//AllAssociatedpagesneedtousepreloadorprefetchresourcesinclude:[]})include的两种用法:1)根据chunk类型处理:include:'asyncChunks'|'initial'|'allChunks'asyncChunks:import()动态导入模块,可以使用prefetch方式异步加载模块;initial:初始化需要的模块;allChunks:处理所有模块(asyncChunks&initial)。2)对于已知名称的chunk,可以使用数组来配置更精确的chunk使用方式包括:['vendor','index']注1)需要配合HtmlWebpackPlugin插件使用-in2)必须放在HtmlWebpackPlugin后面,因为PreloadWebpackPlugin需要使用它提供的hook将构造好的插入html:plugins:[newHtmlWebpackPlugin(),newPreloadWebpackPlugin()]使用的效果一个页面包含的资源最终会在对应页面的头部插入链接标签:实际使用时,由于已经下载到本地,可以直接读取执行,性能大大提升。2.结合import()的好处:拆分chunk,减少首屏js大小。如果项目没有使用HtmlWebpackPlugin,可以对动态导入的资源做如下处理:import(/*webpackPrefetch:true*/)import(/\*webpackPreload:true\*/)【版本限制】Webpackv4.6.0+需要支持预取和预加载。经过本地测试,发现prefetch可用,preload无效。quiklink旨在成为基于用户视口中的链接预取内容的简单解决方案。它是如何工作的通过获取页??面中a标签的href,它会尝试更快地加载接下来可能访问的页面。1)IntersectionObserver(交点观察者):检测当前viewport的linklettarget=document.getElementById('a');io=newIntersectionObserver(entries=>{},{threshold:[1]//当交叉区域为1时会触发回调});io.observe(目标);[备注]通常通过getBoundingClientRect()获取元素在viewport中的详细位置,从而实现滚动加载、吸附等功能。2)requestIdleCallback:等待浏览器空闲;注意它和requestAnimationFrame的区别。3)查看当前网络环境:navigator.connection.effectiveType//4G,2G...;4)提供多种预取链接的方式js库使用了以上四种特性,每一种都值得细细品味。有兴趣的可以看看源码:https://github.com/GoogleChro...。工作流程:1)当浏览器空闲时,获取页面上a标签的所有链接;2)使用IntersectionObserver监控链路;3)使用prefetch下载viewport区域的链接;4)判断当前网络状态,如果你使用的是2G连接或者开启了省流量模式,则不予处理。data-saver:由于数据传输成本高、连接速度慢或其他原因,用户可以启用此类首选项(如果用户代理可用)。题外话:prefetch有点偷流量,用户只消费自己想看的资源对应的资源产生的流量,prefetch会带头擅自下载很多可能不需要的资源(如果你这样做的时候之前流量很贵,估计会被骂。。。)5)下载链接资源,三种下载方式:fetch,xhr,说明只支持prefetch;通过标签获取链接;最好的做法是下载以后可能访问的页面,直接在本地Get执行。总结整体来说PreloadWebpackPlugin更适合处理chunk而不是html文件;而quikLink更适用于类似博客的网站或服务器渲染的页面,从而达到“二次打开”的预期效果。参考preload-webpack-pluginquicklinkIntersectionObserverPreload、Prefetch以及ChromeloadCSSA中的优先级四大CachesReact16加载性能优化指南PWA直出前端同构数亿访问量下直出实践欢迎【扫码】关注,最新文章,不定时更新~