【高性能 JavaScript --笔记】JavaScript 加载与执行

最近在阅读泽卡斯(NicholasC.Zakas)的,《高性能 JavaScript 》。为了加深印象,同时分享知识,以下是我个人阅读理解做的归纳以及笔记。
声明:该书是中阶进阶读的书,如果,你连JavaScript基础语法都不明了,也没必要深读下去。

第一章–>JavaScript 加载与执行

总结:

不喜欢看长篇大论的直接看下面的,内容总结。

  1. 在闭合标签之前,尽量将所有的<script> 标签放在页面底部。这能确保脚本执行之前页面已经渲染完。
  2. 合并脚本,减少 HTTP 请求,这样加载速度更快。
  3. 使用无阻塞下载JavaScript:
    • 使用<script>标签的 defer 属性(有兼容问题)。
    • 使用动态创建的<script>元素来下载执行代码。
    • 使用XHR对象下载 JavaScript 代码并注入页面中。

问题:

    js 在浏览器中阻塞的特性,可以认为是开发者所面临的最严重的可用性问题。在如今多数浏览器都是使用单一进程来处理用户的界面(UI)刷新 和 js 脚本执行。也就是说,同一时刻只能做一件事。js 执行过程耗时越久,浏览器等待响应的事件就越长。
//举个栗子:
<!doctype html>
<html>
  <head>
    <title>Document</title>
    <script src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-2.2.4.js"></script>
  </head>
  <body>
    <p>123</p>
  </body>
</html>
    ↑ 当浏览器遇到<script>标签时,当前 HTML 页面无法获知 js 是否会改变 HTML 结构,还是引入新元素, 或甚至关闭标签。因此,这时浏览器会停止处理页面,先执行 js 代码,然后在解析和渲染页面。
    上面使用了国外的 JQ CDN,加载速度非常的慢,正常情况要 1-4s。这个时候,浏览器呈现一片空白。需要等待 JQ 下载完成,并执行完。才能接着处理页面。
<!doctype html>
<html>
  <head>
    <title>Document</title>
    <script src="file.js1"></script>
    <script src="file.js2"></script>
    <script src="file.js3"></script>
  </head>
  <body>
    <p>123</p>
  </body>
</html>
    ↑ 上面看上去很正常的页面结构,实际上有十分严重的性能问题: 在<head>中加载三个<script>。
    由于脚本会阻塞页面渲染,会直到它们全部下载完成并执行完成后。才会接着渲染页面。

高性能 JavaScript 执行流程
↑ 上图可以看出,第一个 js 文件开始下载,与此同时也会阻塞了页面其他文件下载。此外,从 file.js1 下载完成到 file.js2 开始下载前存在一个延时,这个延时就是执行过程。每一个文件都要等到前一个文件下载并执行完成才会开始下载。这个期间,用户看到的是一片空白。

解决问题

    有问题自然有解决方案,最为简单直接的方法是将<script>标签放在</body>之前。
<!doctype html>
<html>
  <head>
    <title>Document</title>
  </head>
  <body>
    <p>123</p>
    <script src="file.js1"></script>
    <script src="file.js2"></script>
    <script src="file.js3"></script>
  </body>
</html>
    ↑ 将<script>标签放在</body>之前,这时候尽管<script>标签仍然阻塞另一个脚本,但是,页面的大部分内容已经下载完成并显示给了用户,这样显得页面不会加载太慢。这是雅虎特别性能小组提出的优化JavaScript的首要规则:将脚本放在底部。
无阻塞的脚本加载

上面的方法看上去简单容易执行。但是,脚本阻塞其他脚本。不能并行下载的问题依然存在。

    下面介绍无阻塞加载<script>的方法。由于DOM的存在,你可以用 js 动态创建HTML中的几乎所有内容。而我们也能动态的创建<script>元素,来加载 js 。
  var oScript = document.createElement("script");  //创建script元素
  oScript.src = 'file.js1';  //增加url
  document.getElementsByTagName("head")[0].appendChild(oScript);  //插入元素
    ↑ 上面创建<script>元素,并添加url之后,添加到<head>元素中。这种技术的重点在于: 无论在何时启动下载,文件的下载和执行过程都不会阻塞页面其他进程。也就是说如果你添加多个<script>标签,这个下载过程也是并行的。但是,这样也会带来麻烦,看下面的例子。
//加入第一个 <script>
  var oScript1 = document.createElement("script");  
  oScript1.src = 'file.js1';  
  document.getElementsByTagName("head")[0].appendChild(oScript1);
//加入第二个 <script>
  var oScript2 = document.createElement("script");  
  oScript2.src = 'file.js2';  
  document.getElementsByTagName("head")[0].appendChild(oScript2);  
    ↑ 上面加载了两个<script>,那么他们会并行下载 js 文件,下载完成后立即执行(除了 Firefox 和 Opera,他们会等待此前的所有动态脚本节点执行完毕)。脚本’自执行‘,这种机制运行正常。但是,如果 'file.js2' 需要调用 'file.js1' 的代码接口时,但是,'file.js1'还没有下载完毕,这时候,就会出现问题。所以,这种情况下需要跟踪并确保脚本下载完成准备就绪。 
  var oScript1 = document.createElement("script");
  oScript1.onload = function(){
      alert('file.js1加载完成');
  }
  oScript1.src = 'file.js1';  
  document.getElementsByTagName("head")[0].appendChild(oScript1);
    ↑ 上面利用 load 事件,来监听<script>元素是否加载完成。该事件只有 Firefox、Opera、Chorme、Safari 3以上的版本浏览器支持。你没看漏,IE不支持/(ㄒoㄒ)/~~。
    IE支持另一种实现方式,它会触发一个 readystatechange 事件。<script> 元素提供一个 readyState 属性,它的的值随着外链文件的下载过程的不同阶段发生变化,该属性有五种取值:
  • ‘uninitialized’ 初始状态
  • ‘loading’ 开始下载
  • ‘loaded’ 下载完成
  • ‘interactive’ 数据完成下载当尚不可用
  • ‘complete’ 所有数据已准备就

    由于 IE 在标识最终状态 readyState 的值并不一致,有时到达“loaded”状态而不会到达“complete”,有时又不经过“loaded”,就到达“complete”。最好的方法是同时检查两种状态,只要其中之一任何一个触发了,就清除事件处理器(以确保事件不会处理两次)。
    
function loadScript(url, callBack) {
  var oScript = document.createElement("script");
  if (oScript.readyState) {  //IE
    oScript.onreadystatechange = function() {
      if (oScript.readyState == "loaded" || oScript.readyState == "complete") {
        oScript.onreadystatechange = "null";  //清空事件
        callBack();  //回调函数
      }
    }
  } else {// 非 IE
    oScript.onload = function() {
      callBack(); //回调函数
    }
  }
  oScript.src = url;
  document.getElementsByTagName("head")[0].appendChild(oScript);
};
    ↑ 结合前面的例子,写成一个函数,调用方法如下:
loadScript('file.js1',function(){ //先加载第一个
  loadScript('file.js2', function(){  //回调加载第二个
    ...
  })
});
↑ 这段代码会先加载第一个 file.js1,等待 file.js1 加载完成后,在加载 file2.js,以此类推。尽管方案可行,当如果下载的文件较多,可能会带来管理麻烦。

–>未完待续。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值