谷歌 Web 开发最佳实践手册(4.1.4):使用JavaScript增加交互

【伯乐在线提示】:① 5月6日,谷歌开发者中心推出了一个 Web 开发最佳实践手册。伯乐在线资源频道摘编该资源后,已邀请一些关注 Web 开发的朋友参与翻译手册。② 由于译者朋友几乎都是已在职,都是在工作之余参与,每位的翻译进度会不一样(请理解),所以手册中文版不会按照英文版章节顺序发布。③ 手册中文版尚不完整,请不要转载,谢谢合作。


【导读】:JavaScript让我们能够修改页面的每个方面:内容、样式和用户交互行为。然而,JavaScript也能阻塞DOM的构建,并延迟页面的渲染。使你的JavaScript是异步的,并且从关键渲染路径中消除任何不必要的JavaScript,以提供最佳性能。

学习重点

  • JavaScript可以查询和修改DOM和CSSOM
  • CSSOM会阻塞JavaScript执行
  • JavaScript会阻塞DOM的构建,除非明确地声明为异步执行

JavaScript是一种动态的语言,它运行在浏览器中,使我们可以改变页面行为的每个方面:通过增加或移除DOM树上的元素,我们可以修改页面上的内容。我们可以修改每个元素的CSSOM属性,可以处理用户输入等等。为了用实际行动说明这些,在先前的“Hello World”例子中增加一个简单的内联脚本:

查看完整样例

  • JavaScript让我们能得到DOM,并拉取到与之相关的隐藏的span节点—这个节点在渲染树上可能看不见,但它还是存在于DOM中的!然后,一旦得到了这个节点的引用,我们就可以修改它的文本(通过.textContent),甚至覆盖它之前计算好的display属性,从’none’到’inline’。一旦所有的都完成了,页面将会显示’Hello interactive students!’。
  • JavaScript也能让我们创建样式,在DOM中添加或移除元素。实际上,从技术上来说整个页面都可以写成一个大的JavaScript文件,它会一个接一个地创建元素并设计元素的样式 — 那样也能够做到,但是在实际中用HTML和CSS来做更容易一些。在JavaScript方法中的第二部分,我们创建了一个新的div元素,设置了它的文本内容,赋予了它样式,并且把它添加到了body中。

page preview

那样做以后,我们修改了一个已存在的DOM节点的内容和CSS样式,并添加了一个全新的节点到文本中。我们的页面不会赢得任何设计奖,但是它说明了JavaScript提供给我们的能力和灵活性。

然而,在其表面下潜藏着一个巨大的性能隐患。JavaScript提供给我们许多能力,但是它同时也在怎样和何时渲染页面方面产生了许多额外的限制。

首先,注意一下在上面的例子中,内联的脚本是在页面下部的。为什么?好吧,你应该自己试一下,但是如果把脚本移到span元素的上面,你会注意到脚本会执行失败,并报出警告说它无法在文本中找到任何span元素 — 例如,getElementsByTagName(‘span’)会返回null。这证明了一个重要了特性:脚本是在它被插入到文本中的位置处执行的。当HTML解析器遇到一个脚本标签时,它会暂停对构建DOM的处理,并让出控制权给JavaScript引擎。一旦JavaScript引擎完成了运行,浏览器再从之前中断的地方重新开始,并继续进行DOM构建。

换句话说,脚本块找不到任何页面后面的元素,因为他们还没有被创建!或者,略微不同地表达一下:执行内联脚本会阻塞DOM的构建,这也会延迟页面的初次渲染。

把脚本引入到页面中的另一个巧妙的好处是,它们可以读取和修改的不仅仅是DOM,还有CSSOM属性。实际上,那正是我们在例子中所做的,我们把span元素的显示属性从none改变为inline。最后的结果是什么?我们现在有了一个竞争条件。

假如浏览器在我们运行脚本时还没有完成CSSOM的下载和创建会怎么样?答案很简单,并且对于性能不会很好:浏览器会延迟脚本的执行,直到它完成了CSSOM的下载和构建,当我们在等待时,DOM的构建也被阻塞了!

简单来说,JavaScript引入了许多DOM、CSSDOM和JavaScript执行之间的依赖,并且在浏览器如何快速处理和渲染页面到屏幕上时产生了严重的延迟:

  1. 脚本在文本中的位置是很关键的
  2. 当遇到script标签时DOM的构建会被暂停,直到脚本完成了执行。
  3. JavaScript可以查询和修改DOM和CSSOM
  4. 直到CSSOM准备好了,JavaScript才会执行

当我们讨论“优化关键渲染路径”时,更大程度上是在讨论理解和优化HTML、CSS和JavaScript之间的依赖关系。

阻塞解析器和异步的JavaScript

在默认情况下,JavaScript的执行是会“阻塞解析器”的:当浏览器在文本中遇到一个script时,它必须停止DOM的构建,让出控制权给JavaScript来运行,在继续构建DOM之前让脚本来执行。我们已经在前面的例子中使用了一个内联的脚本的行动中看到了这个。实际上,内联的脚本通常是会阻塞解析器的,除非你特别的进行了处理,写了一些额外的代码来延迟它们的执行。

通过script标签引入的脚本是怎样的呢?拿之前的例子来说,把代码抽出来放到一个单独的文件里:

查看完整样例

app.js

当使用<script>标签而不是使用内联的JavaScript片段时,你认为执行顺序会不同吗?当然,答案是“no”,因为他们是一样的,并且以相同的方式表现。在这两种情况下,浏览器都会在它能够处理余下的文本之前,暂停并执行脚本。然而,在使用外部的JavaScript文件的情况下,浏览器将不得不暂停解析,等待从磁盘、缓存或远程服务器上获取这个脚本,这会给关键渲染路径增加数十到数千毫秒的延迟。

从另一个角度来看,这也是一个好消息,给我们带来另一种解决方案!默认情况下,JavaScript是会阻塞解析器的,浏览器并不知道脚本计划在页面上做什么,因此它不得不假定最坏的情况,并阻塞解析器。然而,如果我们可以给浏览器一个标识呢,告诉它脚本不必在它被引入到文件里的位置处执行。这样做就可以让浏览器继续构建DOM,让脚本在它准好以后再执行 — 例如,文件已经从缓存或远程服务器上获取到。

那么,我们怎样来实现这一方法呢?相当的简单,我们可以把脚本标记为async:

添加async关键词到script标签中,在浏览器等待脚本变得可用时,告诉它不必阻塞DOM的构建 — 这是在性能上是一个巨大的优势!

收藏 评论

关于作者:shinancao

(新浪微博:@午后的小甜点) 个人主页 · 我的文章

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部