zoukankan      html  css  js  c++  java
  • 前端性能优化(四)

    使用 JavaScript 添加交互

    JavaScript 允许我们修改页面的方方面面:内容、样式以及它如何响应用户交互。但是,JavaScript 也会阻塞 DOM 构建,延缓页面渲染。我们可以让我们的 JavaScript 异步加载,消除关键渲染路径中不必要的 JavaScript,来提供更佳性能。

    JavaScript 是一门运行在浏览器上的动态语言,它允许我们修改页面上的种种:我们可以向 DOM 树添加或移除元素来修改页面内容,我们可以修改任一个元素的 CSSOM 属性,我们可以处理用户输入,等等。为了实际演示,我们用内联脚本扩展下之前的 “Hello World” 示例:

    <html>
          <head>
            <meta name="viewport" content="width=device-width,initial-scale=1">
            <link href="style.css" rel="stylesheet">
            <title>Critical Path: Script</title>
          </head>
          <body>
            <p>Hello <span>web performance</span> students!</p>
            <div><img src="awesome-photo.jpg"></div>
            <script>
              var span = document.getElementsByTagName('span')[0];
              span.textContent = 'interactive'; // change DOM text content
              span.style.display = 'inline';  // change CSSOM property
              // create a new element, style it, and append it to the DOM
              var loadTime = document.createElement('div');
              loadTime.textContent = 'You loaded this page on: ' + new Date();
              loadTime.style.color = 'blue';
              document.body.appendChild(loadTime);
            </script>
          </body>
        </html>
    

      

    • JavaScript 允许我们进入 DOM,取得隐藏的 span 节点的引用,该节点可能不在渲染树中出现,但它仍在 DOM 里。有了引用后,我们就可以修改它的文本(通过 .textContent),甚至可以将其计算的 display 样式属性从 ‘none’ 改成 ‘inline’。完成上述后,我们的页面现在会显示 “Hello interactive students!“。

    • JavaScript 还允许我们在 DOM 上创建、样式化,然后添加或移除新元素。事实上,技术上说,我们的整个页面可以只是一个大 JavaScript 文件,逐个创建并样式化元素 - 这种方法行得通,但实践中使用 HTML 和 CSS 要简单得多。在我们的 JavaScript 函数的第二部分,我们创建了一个新 div 元素,设置它的文本内容,样式化它,然后将它添加到 body 中。

    页面预览

    这样,我们修改了现有 DOM 节点的内容与 CSS 样式,在文档中添加了一个全新节点。我们的页面不会赢得任何设计奖,但是它演示了 JavaScript 赋予我们的力量与灵活。

    只不过,这里潜藏着一个大的性能问题。JavaScript 赋予我们许多能力,但它也同时给页面如何及何时渲染带来了许多额外限制。

    首先,请注意,上面示例中,我们的内联脚本靠近页面底部。为什么?你应该自己试一把。如果我们把脚本移到 span 元素上方,你会发现脚本不起作用,并提示无法在文档中找到任何 span 元素的引用,即 getElementsByTagName(‘span’) 会返回 null。这透露一个重要事实:我们的脚本在文档的哪儿插入,即在哪儿执行。HTML 解析器遇到一个 script 标签,它会暂停构建 DOM,并移交控制权给 JavaScript 引擎;等 JavaScript 引擎执行完毕,浏览器从中断的地方恢复 DOM 构建。

    换句话说,我们的脚本块找不到页面中靠后的元素,因为这些元素尚未处理到。或者,稍微换个说法:执行内联脚本会阻塞 DOM 构建,也就延缓了首次渲染。

    页面上引用脚本的另一个微妙事实是,它们不仅可以读取、修改 DOM,它们还可以读取、修改 CSSOM。事实上,这也正是我们在例子中所做的,将 span 元素的 display 属性从 none 改为 inline。最终结果?我们现在有一个竞态。

    如果浏览器尚未完成 CSSOM 下载与构建,而我们就想运行我们的脚本,会怎样?答案很简单,对性能不好:浏览器会延迟脚本执行,直到完成 CSSOM 下载与构建,而在我们等待时,DOM 构建同样被阻塞。

    简言之,JavaScript 在 DOM、CSSOM 和 JavaScript 执行间引入了很多新的依存关系,导致浏览器在处理和渲染页面上出现大幅延迟:

    1. 脚本在文档中的位置很重要。
    2. 遇到 script 标签时,DOM 构建停止,直到脚本执行完毕。
    3. JavaScript 可以查询、修改 DOM 和 CSSOM。
    4. CSSOM 准备就绪前,JavaScript 执行被延后。

    我们谈论「优化关键渲染路径」时,很大程度上是在谈论理解、优化 HTML、CSS 与 JavaScript 之间的依存关系谱。

    解析器阻塞 vs. 异步 JavaScript

    默认情况下,JavaScript 执行会阻塞解析器:当浏览器在文档中遇到一个 script,它必须暂停 DOM 构建,移交控制权给 JavaScript 运行时,让脚本先执行,然后才继续处理 DOM。在前面的示例中,我们已经了解内联脚本的情况。事实上,内联脚本始终会阻塞解析器,除非你十分小心,编写额外代码来推迟它们的执行。

    通过 script 标签引入的脚本又怎样?让我们拿前面的示例说,把代码提取到一个单独文件中:

      <html>
          <head>
            <meta name="viewport" content="width=device-width,initial-scale=1">
            <link href="style.css" rel="stylesheet">
            <title>Critical Path: Script External</title>
          </head>
          <body>
            <p>Hello <span>web performance</span> students!</p>
            <div><img src="awesome-photo.jpg"></div>
            <script src="app.js"></script>
          </body>
        </html>
    

      app.js

    var span = document.getElementsByTagName('span')[0];
        span.textContent = 'interactive'; // change DOM text content
        span.style.display = 'inline';  // change CSSOM property
        // create a new element, style it, and append it to the DOM
        var loadTime = document.createElement('div');
        loadTime.textContent = 'You loaded this page on: ' + new Date();
        loadTime.style.color = 'blue';
        document.body.appendChild(loadTime);
    

      你觉得我们使用 <script> 标签代替内联 JavaScript 代码段,执行顺序会有所不同吗?答案是不会,因为这些代码是一样的,所以结果会一样。在两个示例中,浏览器均须先暂停,然后执行脚本,之后才能处理文档的剩余部分。只不过说,在外部 JavaScript 文件情况中,浏览器必须暂停,然后等待脚本从磁盘、缓存或远程服务器中取回,这就又可能给我们的关键渲染路径增加了数以万毫秒计的延迟。

    尽管如此,好消息是,我们有应急出口。默认情况下,所有 JavaScript 均会阻塞解析器,因为浏览器不知道脚本想在页面上做什么,因此它必须假定最糟的状况并阻塞解析器。但是,如果我们能够告知浏览器说,脚本无需在文档中引用它的确切位置被执行呢?如此一来,浏览器会继续构建 DOM,并在脚本准备就绪后(比如,从缓存或远程服务器中加载完文件)执行脚本。

    那么,我们如何实现这种方法呢?很简单,我们可以将脚本标记为 async

     <html>
          <head>
            <meta name="viewport" content="width=device-width,initial-scale=1">
            <link href="style.css" rel="stylesheet">
            <title>Critical Path: Script Async</title>
          </head>
          <body>
            <p>Hello <span>web performance</span> students!</p>
            <div><img src="awesome-photo.jpg"></div>
            <script src="app.js" async></script>
          </body>
        </html>
    

      将 async 关键字添加到 script 标签,告诉浏览器,在它等脚本准备就绪前不应阻塞 DOM 构建,这将是性能上的巨大提升!

  • 相关阅读:
    利用libxml2解析xml文档
    找出两个链表的第一个公共结点
    [转载]风雨20年:我所积累的20条编程经验
    inotify监测文件及文件夹
    [转载]linux下svn常用指令
    利用zlib进行数据压缩
    2013腾讯编程马拉松初赛:郑厂长系列故事——体检
    Socket编程之简单介绍
    C语言中static的作用总结
    写程序实现wireshark的抓包功能
  • 原文地址:https://www.cnblogs.com/LiJianBlog/p/4814248.html
Copyright © 2011-2022 走看看