web浏览器停止运行此脚本 优化浏览器前端
译者 | 京东金融-移动研发部-前端工程师 田腾
原作者 |Sanjay Purswani
来源 |Software Engineer at comparethemarket.com
优化事关速度和满意度
为了提升用户体验(User Experience,UX),我们希望前端提供快速加载和执行的网页。而对于提升开发者体验(Developer Experience, DX)来说,我们希望前端能够快速,简便和实用。
这样的优化不仅使我们的用户和开发者满意,也会显着提高SEO排名, 因为Google的SEO排名会偏向于优化较好的页面。如果你已花费了大量时间来提升自己网站的Google Pagespeed Insights分数,那么本文将有望揭示分数的实际意义以及为优化前端我们采取的大量策略。
背景
最近,我的整个团队有机会花费一些时间升级到我们的代码库,该升级可能使用React技术。这让我思考我们应该如何建立前端项目。很快我意识到浏览器将是我们考虑升级方法中的一个重要因素,同时也是我们知识的一大瓶颈。
方法首先
我们不能控制浏览器或者改变它的行为方式,但是我们可以理解它的工作原理,用来优化我们页面的加载。
幸运的是,浏览器行为的基本原理相当稳定并有据可查,长时间内也不会显着改变。
这些特点至少给了我们一个努力的目标。
其次
另一方面,代码,堆栈,结构和模式是我们可以控制的。他们更灵活,改变地更快,为我们提供更多的选择。
因此
我决定彻底地弄清楚我们的代码最终结果应该是什么样,然后形成一个写这种代码的意见。 在这第一篇文章中,我们将专注于了解浏览器。
浏览器的功能
我们来建立一些知识。以下是我们期望浏览器运行的一些微不足道的HTML。
浏览器如何渲染页面
当浏览器收到我们的HTML时,会解析它,将其分解成一个自己理解的词汇,得益于HTML5 DOM规范,这些词汇在所有浏览器中保持一致。然后,浏览器将通过一系列步骤来构建和渲染页面。以下是高度概括的描述。
使用HTML创建文档对象模型(DOM)。
使用CSS创建CSS对象模型(CSSOM)。
执行DOM和CSSOM上的脚本(scripts)。
结合DOM和CSSOM形成渲染树(render tree)。
使用渲染树布局(layout)所有元素的大小和位置。
在所有像素中绘制(paint)。
第一步 – HTML
浏览器从上到下读取HTML的标记,将其分解成一个个节点来创建文档对象模型。
HTML优化策略样式写在顶部,脚本写在底部
虽然这个规则使用起来也有例外和细微差异,但一般的想法是尽可能早地加载样式,尽可能晚地加载脚本。这样做的是因为脚本需要在HTML和CSS完成解析后再执行,因此我们将样式放到最前面,以便在编译和执行底部的脚本之前,有充足的时间来计算样式。 接下来我们进一步研究如何调整这个优化。
精简和压缩
这适用于我们提供的所有内容,包括HTML,CSS,JavaScript,图像和其他资源。精简删除任何冗余字符,包括空格,注释,额外的分号等。
压缩,例如GZip,将代码或其他资源中重复的数据,替换为指向原始实例的指针。大幅压缩下载文件的大小,而是依靠客户端解压缩文件。
通过这两项处理,你有望将有效载荷减少80%或90%。例如,将bootstrap减少87%。
可访问性
虽然这不会使你的网页下载速度更快,但会大大提高有身体缺陷用户的满意度。确保为所有人提供可用功能!在元素上使用aria标签,在图像上提供替代文本以及所有其他的不错功能()。使用像WAVE这样的工具来识别你可提高可访问性的地方。
第二步 - CSS
当浏览器找到任何与样式相关的节点,例如,外部,内部或内联样式时,它会停止渲染DOM并使用这些节点创建CSSOM。这就是为什么人们说CSS“阻塞渲染”。以下是不同类型样式利弊。
CSSOM节点的创建就像DOM节点一样,稍后它们将被组合起来,但现在它们是下面这个样子。
CSSOM的构造阻塞了页面的渲染,所以我们希望在渲染树中尽早地加载样式,使它们尽可能轻巧,并在有效的时候延迟加载。
CSS优化策略使用媒体属性(@media)。
媒体属性指定加载样式需要满足的环境,例如,是否有最大或最小分辨率?是屏幕阅读器吗?
台式机非常强大,但移动设备不是,所以我们想要给他们最轻的有效载荷。我们可以假设一开始的时候只提供移动样式,然后将台式机上的样式放在媒体属性里,这样做不会阻止下载针对台式的机样式,但这些样式不会阻塞我们移动端页面的加载或耗尽移动端浏览器宝贵的资源。
延迟加载CSS
如果你有样式,可以等到第一个有意义的绘制之后再加载和计算,例如,在首屏看不到的内容,或者在页面响应之前不需要的内容。在添加样式之前,你可以利用脚本等到页面需要的时候再加载这部分样式。
这是如何实现的一个例子(),另一个例子()。
较少的选择器
将更多的元素链接在一起存在一个明显的缺点,即将会有更多的数据需要传输,从而扩大了CSS文件,另一方面,更多的选择器也会使客户端计算样式时消耗大量计算。
只提供你需要的
如果你作为前端工作了一段时间,就会知道CSS中的一个大问题是删除内容时的不可预测性。它设计时就被诅咒会不断增大。
要尽可能地精简CSS,请使用诸如uncss之类的工具web浏览器停止运行此脚本,或者可以寻找类似的网站,有很多类似的选择。
第三步 - JavaScript
然后,浏览器会在找到任何JavaScript节点时继续构建DOM / CSSOM节点,即找到外部或内联脚本。
因为我们的脚本可能需要访问或操作前面的HTML或样式,所以我们必须等到二者全部构建完成。
因此浏览器必须停止解析节点,先完成CSSOM构建,执行脚本,然后继续解析。这就是为什么人们会说JavaScript“阻塞解析”。
浏览器有一个称为“预加载扫描器”的东西,它将扫描DOM脚本并开始预加载它们,但脚本只有在构建了先前的CSS节点之后才顺次执行。
如果这是我们的脚本:
那么这将对我们的DOM和CSSOM造成如下影响:
JavaScript优化策略
优化脚本是我们可做的最重要的事情之一,也是大多数网站做得最糟糕的事情之一。
异步加载脚本
通过在我们的脚本上使用一个异步属性(async),可以告诉浏览器先不管这个脚本,以较低的优先级在另一个线程下载它,不要阻塞页面其余部分加载。一旦完成下载,这个脚本将被执行。
这意味着该脚本可能在任意时间执行,这会导致两个明显的问题。首先,它可能在页面加载之后执行很久,所以如果我们依靠它为用户体验做一些事情,那么我们可能会给我们的用户一个不太好的体验。其次,如果它在页面加载完成之前执行,我们无法预测它是否能够访问正确的DOM / CSSOM元素并可能会中断执行。
async属性对于不影响我们的DOM或CSSOM的脚本是非常好的,对不需要我们的代码内容的外部脚本也是非常好,对于其他影响用户体验的脚本来说不是必需的,例如分析或跟踪。但是如果你发现任何合适的场景,请使用它。
延迟加载脚本
defer属性非常类似于async属性,它也不会阻止我们的页面的加载,但是,它会等到我们的HTML解析完成后再顺次执行。
这对于将对我们的渲染树上起作用的脚本来说是一个非常好的选择,但是对于加载首屏内容,或者先前的脚本运行完成后再执行的情况并不重要。
这是使用延迟策略的另一个非常好的选择(),也可以使用类似addEventListener这样的东西。如果你想知道更多,那么这是一个开始阅读的好地方()。
不幸的是async属性和defer属性不适用于内联脚本,因为默认情况下,浏览器将会一旦遇到内联脚本就好编译并执行它们。当他们在HTML中内联时,它们立即运行,通过使用外部资源上的上述两个属性,相较于DOM / CSSOM我们可以延迟运行脚本。
操作前克隆节点
当且仅当您在对DOM进行多次更改时遇到意外行为时,尝试此操作。首先克隆整个DOM节点,对克隆的节点进行更改,然后更换原始节点可能会更有效,因为这会避免多次重绘降低CPU效率和内存负载。它还可以防止您的页面“抖动”和闪烁的未加载样式内容(FOUC)。
克隆时请小心,因为它不会克隆事件侦听器。有时候,这可能正是你想要的。在过去,当不调用命名函数,没有JQuery的.on()和.off()方法可用时,我们使用这种方法来重置事件侦听器。
预加载/预读取/预提交/预连接
这些属性基本上都是在他们的测试版本中,非常好用。但是它们是相当新的,并没有广泛的浏览器支持,这意味着我们大多数人并不是真的很重要的备选方法。但如果你有想了解,请看看这里()和这里()。
第四步 - 渲染树
一旦所有节点都被读取,并且DOM和CSSOM已准备好组合,浏览器就构建“渲染树”。如果我们将节点视为单词,将对象模型视为句子,则“渲染树”就是整个页面。现在浏览器具有渲染页面所需的一切。
第五步 - 布局
然后我们进入布局阶段,确定页面上所有元素的大小和位置。
第六步 – 绘制
最后我们进入绘制阶段,我们实际上光栅化了屏幕上的像素,为我们的用户绘制页面。
所有这些通常发生在几秒或零点几秒内。我们的工作就是做得更快。
如果JavaScript事件更改页面的任何部分,就会重新渲染“渲染树”,并强制我们再次布局和绘制。现代浏览器足够聪明,只会进行部分重绘,但是我们不能依赖此功能来保障我们的页面高效。
尽管如此,JavaScript在客户端主要是基于事件的,我们希望它处理我们的DOM,它仍将做这些处理。我们只需要限制它的不良影响。
现在你应该充分了解领会 Tali Garsiel的这个演讲。这是2012年的演讲,但信息仍然适用。她关于这个问题的全面论文可以在这里()读到。
如果你喜欢迄今为止所读过的内容,并仍然渴望了解更多底层技术知识,那么你所有知识的指导就是HTML5规范()。
我们快要完成整个介绍,只需再多给我一点点时间!现在我们揭示为什么我们需要知道上以上内容。
浏览器如何进行网络调用
在本节中,我们将了解如何最有效地向浏览器传送渲染页面所需的数据。
当浏览器向URL发出请求时,我们的服务器使用一些HTML进行响应。我们将从细微处开始介绍,慢慢增加复杂性。
假设这是我们页面的HTML。
我们需要学习一个新的术语,关键渲染路径(Critical Rendering Path,CRP)。它是指浏览器渲染页面所需的步骤数。这是我们的CRP图解现在看起来的样子。
浏览器发出GET请求,然后一直处于空闲状态,直到我们回复我们的页面所需的1kb HTML(还不包括CSS或JavaScript),然后可以构建DOM并渲染页面。
关键路径长度
三个CRP指标中的第一个是路径长度。我们希望这个值尽可能低。
浏览器需要一次往返请求服务器来检索渲染页面需要的HTML,它只需请求这一次。因此,我们的关键路径长度是1,完美。
现在我们继续下一个等级,在HTML里包含一些内部样式和JavaScript。
如果检查我们的CRP图表,我们会看到几个变化。
我们有两个额外的步骤,构建CSSOM和执行脚本。这是因为我们的HTML包含需要计算的内部样式和脚本。然而,由于不需要外部请求,因此它们不会增加关键路径的长度,耶! 但等一下,不要高兴得太早。还要注意,我们的HTML大小增加到2kb,所以我们必须在某个地方采取措施。
关键字节
三个指标中的第二个是关键字节。它测量渲染页面所需要传输的字节数。不是所有字节都需要下载到页面上,只有实际渲染页面中需要,及响应用户的内容才需要下载。
不用说,我们同样想尽量降低这个指标。
如果你认为这很好,不需要使用外部资源,那么你是错的。虽然这看起来很诱人,但它在规模上是不可行的。实际,如果我的团队把提供页面所需要的内容都放在内部或者行间,这个文件会变得非常大。并且浏览器的构造也不能够处理这么大的负载。
看看这个关于内联所有React recommends样式对页面加载的影响的有趣文章()。 DOM变成四倍大小web浏览器停止运行此脚本,需要两倍的时间才能安装,响应时间增长50%。这当然不能接受。
还要考虑外部资源可以缓存的事实,因此在再次访问页面,或访问其他使用相同资源(例如my-global.css)的页面时,浏览器将不会进行网络调用,而是使用缓存资源,这为我们赢得更大的胜利。
所以让我们更进一步,将样式和脚本引用为外部资源。请注意辅助论坛,我们有1个外部CSS文件,1个外部JavaScript文件和1个外部异步JavaScript文件
这是我们的CRP图解现在是这样。
浏览器获取页面,构建DOM,一旦碰到外部资源,预加载扫描器启动。它继续扫描并开始下载在HTML可以找到的所有外部资源。 CSS和JavaScript高优先级,其他资源优先级低一些。
它遇到我们的styles.css和app.js,就开辟出另外一个关键路径以获取它们。它没有加载analytics.js,因为我们给了它async属性。浏览器会在另一个线程以较低的优先级下载它,但是因为它不会阻止我们的页面渲染,因此它不会影响关键路径。这正是Google自己的网站优化排名算法。
关键文件
CRP指标的最后一项是关键文件。浏览器渲染页面必须下载的文件总数。在我们的第三个例子中,关键文件是HTML文件本身,CSS和JavaScript。异步脚本不算在内。这个指标当然越小越好。
再看看关键路径长度
现在你可能会认为这是关键路径最长的大小?我是指我们只需要下载HTML,CSS和JavaScript来渲染我们的页面,而且我们在两次请求中就完成了。
来源:【九爱网址导航www.fuzhukm.com】 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!