本文是阅读《高性能JavaScript》一书后,从 加载和执行 模块对JavaScript性能优化做了部分总结,记录一下。可能总结的不好,不是很完整,也希望各位大佬能多给出一些建议。万分感谢!

多数浏览器使用单一进城来处理用户界面(UI)刷新和JavaScript脚本执行,所以同一时刻只能做一件事。JavaScript执行过程耗时越久,浏览器等待相应的时间就越长。
简单说,这意味着<script>标签每次出现都霸道地让页面等待脚本的解析和执行,无论当前的JavaScript代码是内嵌还是包含在外链文件中,页面的下载和选人都必须听下来等待脚本执行完成。

脚本位置

  • 将所有的<script>标签尽可能的放到<body>标签的底部(雅虎特别性能小组提出的优化JavaScript的首要规则)

组织脚本

  • 浏览器在解析HTML页面的过程中每遇到一个<script>,都会因执行脚本而导致一定的演示,因此最小化延时时间会名校改善页面的总体性能

    建议永远不要把内嵌脚本紧跟在<link>标签后面,因为将一段内嵌脚本放在引用外联样式表的<link>标签之后会导致页面阻塞去等待样式表的下载。这么做是为了确保内嵌脚本在执行时能获得最精准的样式信息

  • 减少页面中外链脚本的数量,将多个文件合并成一个

无阻塞的脚本

意味着页面加载完成之后才加载JavaScript代码

  • <script>标签添加 async 属性,则资源下载完成自动执行

延迟的脚本

  • <script>标签添加 async 属性,则资源下载完成也必须等到页面加载完成后执行

    PS:async和defer的共同点和区别:
    共同点:采用并行下载,在下载过程中不会产生阻塞
    区别:执行时机不同,async是加载完成后自动执行,而defer需要等待页面完成后执行
    注意: 使用defer和async时最好先查阅当前浏览器是否支持该属性

动态脚本元素

使用动态代码操作DOM创建<script>元素,这种技术的重点在于:无论何时启动下载,文件的下载和执行过程不会阻塞页面其他进行。设置可以将代码放在<head>区域而不会影响页面其他部分

注意点

  • 创建的<script>标签添加到<head>标签里比添加到<body>里更保险

    • 因为当<body>中的内容没有全部加载完成时,IE可能会抛出一个“操作已终止”的错误信息
  • 当脚本“自执行”时,这种机制运行正常。但是当代码只包含供页面其他接口调用的接口时,就会有问题

    • 在这种情况下,必须跟踪并确保脚本下载完成并准备就绪

    • 实现代码

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      function loadScript(url, callback) {
      var script = document.createElement('script);
      script.type = "text/script";
      if (script.readyState) { // IE
      script.onreadystatechange = function() {
      if (script.readyState == "loaded" || script.readyState == "complete") {
      // 确保事件不会处理两次
      script.onreadystatechange = null;
      callback();
      }
      }
      } else { // 其他浏览器
      script.onload = function() {
      callback();
      }
      }
      script.src = url;
      document.getElementsByTagName("head")[0].appendChild(script);
      }
    • 调用方法

      1
      2
      3
      4
      5
      6
      7
      loadScript("file1.js", function() {
      loadScript("file2.js", function() {
      loadScript("file2.js", function() {
      alert("所以文件加载完成");
      })
      })
      })

XMLHTTPRequest脚本注入

  • 此技术会先创建一个XHR对象,然后用它下载JavaScript文件,最后通过动态<script>元素将代码注入到页面中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var xhr = new XMLHttpRequest();
    xhr.open("get", "file1.js", true);
    xhr.onreadystatechange = function() {
    if (xhr.readyState == 4) {
    if (xhr.status >= 200 && xhr.statue < 300 || xhr.status == 304) {
    var script = document.createElement("script");
    script.type = "text/javascript";
    script.text = xhr.responseText;
    document.body.appendChild(script);
    }
    }
    }
  • 优点:可以下载JavaScript代码但不立即执行,由于代码实在标签之外返回的,因此下载后不会自动执行,可以将脚本的执行推迟到页面准备好的时候

  • 局限性:JavaScript代码必须与所请求的页面处于相同的域,这意味着JavaScript文件不能从CDN下载

推荐的无阻塞模式

向页面中添加大量JavaScript的推荐方式只需两步:先动态加载所需的代码,然后加载初始化页面所需的剩下的代码。第一部分的代码尽量精简

  • 第一种做法

    1
    2
    3
    4
    5
    6
    <script type="text/javascript" src="loader.js"></script>
    <script type="text/javascript">
    loadScript("the-rest.js", function() {
    Application.init();
    })
    </script>
  • 第二种做法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    <script type="text/javascript">
    function loadScript(url, callback) {
    var script = document.createElement('script);
    script.type = "text/script";
    if (script.readyState) { // IE
    script.onreadystatechange = function() {
    if (script.readyState == "loaded" || script.readyState == "complete") {
    // 确保事件不会处理两次
    script.onreadystatechange = null;
    callback();
    }
    }
    } else { // 其他浏览器
    script.onload = function() {
    callback();
    }
    }
    script.src = url;
    document.getElementsByTagName("head")[0].appendChild(script);
    }
    loadScript("the-rest.js", function() {
    Application.init();
    })
    </script>

第二种做法可以比多产生一次HTTP请求,但是建议使用YUI Compressor 把初始化代码压缩到最小尺寸

YUI3 的方式

  • YUI3核心设计理念:由页面中的少量代码来加载丰富的功能组件

  • 使用

    1
    2
    3
    4
    5
    6
    7
    8
    <script type="text/javascript" src="http://yui.yahooapis.com/combo?3.0.0/build/yui/yui-min.js"></script>
    <script type="text/javascript">
    // dom实际上拼装出一个带有所有依赖文件组合的URL
    // Y,当前YUI实例
    YUI.use("dom", function(Y) {
    Y.DOM.addClass(document.body, "loaded")
    })
    </script>

LazyLoad 类库

  • 使用
    1
    2
    3
    4
    5
    6
    7
    <!-- LazyLoader 源代码:http://github.com/rgrove/lazyload -->
    <script type="text/javascript" src="lazyload-min.js"></script>
    <script>
    LazyLoad.js(["first-file.js", "the-rest.js"], function() {
    Application.init()
    })
    </script>

LABjs

  • 使用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <!-- LABjs源代码:http://labjs.com -->
    <!-- 虽然是同归.script链式调用,其实也是并行下载 -->
    <script type="text/javascript" src="lab.js"></script>
    <script>
    // wait 意味着加载好前面的javascript资源才会继续向下加载
    $LAB.script("first-file.js").wait()
    .script("the-rest.js")
    .wait(function () {
    Appication.init();
    })
    </script>

小结

减少JavaScript对性能的影响的方式:

  • </body>闭合标签之前,将所有的<script>放到页面底部。这能确保在脚本执行前页面已经完成了渲染

  • 合并脚本。页面中的<script>标签越少,加载也就越快,响应也更迅速,无论外链文件还是内嵌脚本都是如此

  • 有多种无阻塞下载JavaScript的方法:

    • 使用<script>标签的defer属性

    • 使用动态创建的<script>元素来下载并执行代码

    • 使用XHR对象下载JavaScript代码并注入到页面中