一、阻塞特性

《高性能JavaScript》一书中,关于第一章“Loading and Execution”,提到了无阻塞加载JavaScript技术,目的是为了提高页面呈现速度。

说到无阻塞加载JavaScript要点,我们就有必要知道,为什么在html中不管是内联JavaScript还是外联,会影响到页面的性能?

原因是:JavaScript是单线程,在JavaScript运行时其他的事情不能被浏览器处理。事实上,大多数浏览器使用单线程处理UI更新和JavaScript运行等多个任务,而同一时间只能有一个任务被执行。所以在执行JavaScript时,会妨碍其他页面动作。这是JavaScript的特性,我们没法改变。

并且,html解析过程是至上而下的,当html解析器遇到诸如<script>、<link>等标签时,解析器就会停止下来,去下载相应的内容。需要注意的是,在加载<script>、<link>标签时都会阻止解析器往下执行。

并且,html解析过程是至上而下的,当html解析器遇到诸如<script>、<link>等标签时,就会去下载相应内容。且加载、解析、执行JavaScript会阻止解析器往下执行。

那什么时候,html解析器才能往下继续解析html文档呢?

就JavaScript而言,当html解析器遇到<script>标签,无论它是内联还是外联,页面中的下载和解析过程都必须停止,直到<script>从外部加载进来的JavaScript或内联的JavaScript运行完毕,方可继续解析。在高版本的浏览器当中,允许并行下载JavaScript文件,当一个<script>标签正在下载外部资源时,不必阻塞其他<script>标签,但是不幸地是,JavaScript的下载仍然会阻塞其他资源的下载,例如图片。这里还需要值得注意的是,对于样式和脚本的先后顺序同样会影响到浏览器的解析过程,比如将<link>标签放在<script>标签前面,如果样式下载受阻,那么将阻塞<link>后面的<script>加载和执行,究其原因主要在于:script脚本在执行过程中可能会引用到相关样式。

了解了JavaScript在html中的阻塞特性,我们再来看看如何改善其阻塞特性。

二、改善方法

--最简单做法--:

为了让html文档在解析时,尽量地快,常规的做法是将<script>标签放到</body>标签的前面,这样就不会阻塞html中其他资源的下载了。

如下:

尽管脚本下载之间互相阻塞,但页面已经下载完成并且显示在用户面前了,进入页面的速度不会显得太慢。且,为了让脚本之间的互相阻塞最小化,通常将多个相关的JavaScript文件合并为一个JavaScript文件,另外这样做带来的好处不仅让脚本之间阻塞变小,还减少了http请求的数量。

但,这样做JavaScript文件下载之间还是会阻塞,特别是当JavaScript文件逐渐变多时。

故而,引入无阻塞脚本技术。

无阻塞脚本技术主要分为两大类:

  1、  HTML5中的defer和async;

  2  动态创建script为dom元素。

下面将分别介绍。

--HTML5中的defer和async--:

HTML5中提供了两个属性供<script>标签使用,目的就是为了无阻塞加载JavaScript。

用法如下:

<script src="file1.js" defer></script>
<script src="file2.js" async></script>

需要注意的是,这两个属性对内联JavaScript是无效的,只针对外联JavaScript,如上所示。

加载流程:

当解析器遇到设置defer或者async属性的<script>元素时,它开始下载脚本,并继续解析文档。脚本会在它下载完成后尽快执行,但是解析器没有停下来等待他下载。

defer和async区别:

就defer和async的区别而言,使用defer的<script>标签是按照他们排列的顺序执行的,而使用async的<script>标签是不按他们在HTML中的排列顺序执行的;

就执行时间而言,defer是在DOMContentloaded事件之前执行,而async是在window.onload事件之前执行的,且只支持IE10+。当defer和async同时存在时,会忽略defer而遵循async。且使用defer和async的脚本禁止使用document.write方法哦。

--动态脚本元素--:

因为script标签是在html中的,是属于dom元素,所以我们完全可以利用dom方法创建一个动态的script元素。

如下:

var script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'file1.js';
document.getElementsByTagName('head')[0].appendChild(script); 

当创建的script元素添加到页面后立刻开始下载。此技术的重点在于:无论在何处启动下载,文件的下载和运行都不会阻塞其他页面的处理过程。你甚至可以将这些代码放在<head>部分而不会对其余部分的页面代码造成影响(除了用于下载文件的HTTP连接)

上面加粗部分引至《高性能JavaScript》,当时在我读到这句话时,不是很理解,在前面“阻塞特性”一小节中,我们提到JavaScript是单线程且与UI线程互排,那么JavaScript在运行时,怎么不会阻塞其他页面的处理过程呢?

为此,带着这一困惑在博客园问答中心提出了自己的观点并与道友讨论(‘博问点击此’)

通过与道友讨论以及自己查看了相关文档后,有了自己见解:

之所以动态创建script元素去加载JavaScript文件,不会对页面其余操作影响,原因如下:

  1、html解析器将script当做了dom元素,而不是script标签,所以就不对其进行诸如加载、解析、运行时,停止页面中一切行为。打了个擦边球。

  2、JavaScript是单线程,且与UI线程共享同一个线程,但这不代表浏览器就只有一个线程。所以在执行JavaScript代码时,不影响图片之类的下载。

好了,回到刚才采用动态脚本元素的方法,我们还得完善下,原因是上述代码,在‘自运行’时还好,但是如果引用了其他js文件中的方法呢?那就得出错咯。因为我们无法保证动态脚本元素执行JavaScript代码的顺序。针对这一问题,标准浏览器我们可以利用<script>节点的load事件处理,而IE浏览器我们可以利用其特有的readystatechange事件处理。

封装好的代码如下:

function loadScript(url, callback){
var script = document.createElement('script');
script.type = 'text/javascript';
/*
在IE中readyState值所表示的最终状态并不一致,
有时<script>元素会得到"loaded"却不出现"complete",
但另外一些情况下出现"complete"而用不到"loaded"。
最安全的办法就是在readystatechange事件中检查这两种状态,
并且当其中一种状态出现时,删除readystatechange事件句柄(保证事件不会被触发两次)
*/
if(script.readyState){//IE
script.onreadystatechange = function(){
if(script.readyState == 'loaded' || script.readyState == 'complete'){
script.onreadystatechange = null;
callback()
}
}
}else{//Other
script.onload = function(){
callback();
}
}
script.src = url;
document.getElementsByTagName('head')[0].appendChild(script);
}

所以,当页面中动态加载多个有关联的JavaScript文件时,我们可以将其串联起来,保证顺序。

如下:

//串联起来
loadScript('file1.js',function(){
loadScript('file2.js',function(){
...
});
});

除开这种方法,还有一种就是“XHR脚本注入”,大体内容与上面的方法差不多,都需要动态创建script元素,区别在于该方法利用XMLHttpRequest对象,请求JavaScript文件,并将请求到的responseText,插入script元素的text中。因为是借助XMLHttpRequest对象,缺点显而易见,不能跨域请求。

示例代码如下:

var xhr = new XMLHttpRequest();
xhr.open('get', 'file1.js', true);
xhr.onreadystatechange = function(){
if(xhr.readyState == 4){
if(xhr.status >= 200 && xhr.status < 300 || xhr.status ==304){
var script = document.createElement('script');
script.type = 'text/javascript';
script.text = xhr.responseText;
document.body.appendChild(script);
}
}
};
xhr.send(null);
三、拓展阅读

[1] JavaScript是单线程的深入分析

[2] HTML渲染过程详解

[3] 浏览器加载渲染网页过程解析

[4] defer、async属性以及JS异步加载并执行解决方案

[5] HTML5 <script>元素async,defer异步加载

[6] 无阻赛JavaScript脚本技术

JavaScript阻塞剖析与改善的更多相关文章

  1. javascript prototype 剖析

    学过javascript的一定对prototype不陌生,但是这个究竟是个什么东西,就不一定很清楚. 我们先对prototype进行一个定义:每个函数都有一个prototype属性,这个属性是指向一个 ...

  2. js1:对象的学习,构造函数,继承构造函数【使用教材:JavaScript深度剖析第2版】

    原文发布时间为:2008-11-08 -- 来源于本人的百度文章 [由搬家工具导入] <html> <head> <title>js</title> & ...

  3. 探真无阻塞加载javascript脚本技术,我们会发现很多意想不到的秘密

    下面的图片是我使用firefox和chrome浏览百度首页时候记录的http请求 下面是firefox: 下面是chrome: 在浏览百度首页前我都将浏览器的缓存全部清理掉,让这个场景最接近第一次访问 ...

  4. 更优雅的方式: JavaScript 中顺序执行异步函数

    火于异步 1995年,当时最流行的浏览器--网景中开始运行 JavaScript (最初称为 LiveScript). 1996年,微软发布了 JScript 兼容 JavaScript.随着网景.微 ...

  5. JavaScript 如何工作:渲染引擎和性能优化技巧

    翻译自:How JavaScript works: the rendering engine and tips to optimize its performance 这是探索 JavaScript ...

  6. 前端性能优化:细说JavaScript的加载与执行

    本文主要是从性能优化的角度来探讨JavaScript在加载与执行过程中的优化思路与实践方法,既是细说,文中在涉及原理性的地方,不免会多说几句,还望各位读者保持耐心,仔细理解,请相信,您的耐心付出一定会 ...

  7. JavaScript学习(零)前引

    一)概述 JavaScript是一个面向web的编程语言,一种解释性语言,边执行边解释.也是一种基于对象(Object)和事件驱动(EventDriven)的,安全性好的脚本语言,语法和java类似. ...

  8. 以优美方式编写JavaScript代码

    英文原文:CoffeeScript: The beautiful way to write JavaScript 我用 JavaScript 编程很多年了,写了大量的 JavaScript 代码,即便 ...

  9. 再谈DOMContentLoaded与渲染阻塞—分析html页面事件与资源加载

    浏览器的多线程中,有的线程负责加载资源,有的线程负责执行脚本,有的线程负责渲染界面,有的线程负责轮询.监听用户事件. 这些线程,根据浏览器自身特点以及web标准等等,有的会被浏览器特意的阻塞.两个很明 ...

随机推荐

  1. ThreadPoolExecutor源码学习(2)-- 在thrift中的应用

    thrift作为一个从底到上除去业务逻辑代码,可以生成多种语言客户端以及服务器代码,涵盖了网络,IO,进程,线程管理的框架,着实庞大,不过它层次清晰,4层每层解决不同的问题,可以按需取用,相当方便. ...

  2. WebService的工作原理

    Web Service全称XML Web Service WebService是一种可以接收从Internet或者Intranet上的其它系统中传递过来的请求,轻量级的独立的通讯技术.是:通过SOAP ...

  3. chrome浏览器js 导出excel

    <table id="table"> <tr> <th>ID</th> <th>姓名</th> <th ...

  4. Linux 安装PHP PECL 百分百成功

    1.  下载 需要安装的组件 http://pecl.php.net/packages.php 2.  解压  tar zxf 你的扩展包路径 3.  进入你解压的扩展包路径后 访问 /usr/bin ...

  5. Web 入门之 XML

      160916   1. 什么是XML?   XML 是 EXtensible Markup Language 的缩写,称为可扩展标记语言,所谓可扩展指用户可根据XML规则自定义标记.例子1-1 = ...

  6. 【codevs】刷题记录→_→(推荐看!)

    注:本文是我原先在csdn内写的一篇博文,现转到这里,两篇博文尽量同时更新. //#include<iostream->shuati> //define 为什么刷  学长☞hzwer ...

  7. “眉毛导航”——SiteMapPath控件的使用(ASP.NET)

    今天做网站的时候,用到了SiteMapPath控件,我把使用方法记录下来,以便日后查阅以及帮助新手朋友们. SiteMapPath”会显示一个导航路径(也称为痕迹导航或眉毛导航),此路径为用户显示当前 ...

  8. 用C#语言在Visual Studio 2010里开发一个自定义的PowerShell Cmdlet

    1. 打开Visual Studio 2010 2. 新建一个基于Class Library的项目 3. 给项目起个名字然后OK 4. 为项目添加下列Reference System.Manageme ...

  9. Tempdb initial size和dbcc shrinkfile

    在使用sql server时您可能遇到过下面的情况,tempdb的数据文件初始大小为3mb, 随着对tempdb的使用,tempdb文件逐渐变得很大(例如30GB),导致了磁盘空间不足. 此时您需要立 ...

  10. SQL Server2014 哈希索引原理

    SQL Server2014 哈希索引原理 翻译自:http://www.sqlservercentral.com/blogs/sql-and-sql-only/2015/09/08/hekaton- ...