一、阻塞特性

《高性能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. 深入理解JavaScript中 fn() 和 return fn() 的区别

    在js中,经常会遇到在函数里调用其它函数的情况,这时候会有 fn() 这种调用方式,还有一种是 return fn() 这种调用方式,一些初学者经常会一脸萌逼地被这两种方式给绕晕了.这里用一个优雅的面 ...

  2. Errors occurred during the build. Errors running builder 'JavaScript Validator' on project

    1.问题:Errors occurred during the build. Errors running builder 'JavaScript Validator' on project 2.解决 ...

  3. java的基础知识运算符

    一.运算符. 1.算数运算符:+,-,*,/,% 2.自增自减 :++ ,-- ++在前 先运算在赋值 ++在后 先赋值后运算 -- 减减同上. 3.赋值运算符 : = ,+=,-=,*=,/= 4. ...

  4. JS二维数组排序组合

    需求是这样的:http://q.cnblogs.com/q/29093/ 这里简述一下: 现在有一个不确定长度的数组.比如:var temp=[["Fu","Hai&qu ...

  5. c# http get请求与post请求实例

    //http请求工具类 using System;using System.Collections.Generic;using System.IO;using System.Linq;using Sy ...

  6. openGL实现二维图形和三维图形

    openGL是一个强大的底层图形库,其命令最初的时候使用C语言实现的.openGL定义了一个图形程序接口,常用于制作处理三维图像,功能强大,调用方便,在图像处理十分受欢迎. 实现图形主要使用的是ope ...

  7. iOS 琐碎点------切某个或某几个角的圆角

    不说废话----------> 1.如果是切四个角的圆角,代码示例: self.picImage.layer.cornerRadius = 8; self.picImage.layer.mask ...

  8. MySQL主从复制技术的简单实现

    配置环境: 主从服务器操作系统均为 ubuntu15.10 主从服务器MySQL版本均为 MySQL5.6.31 主服务器IP:192.168.0.178 从服务器IP:192.168.0.145 主 ...

  9. AWS 免费套餐

    AWS 免费套餐 转载自:https://aws.amazon.com/cn/free/?sc_channel=PS&sc_campaign=acquisition_CN&sc_pub ...

  10. .NET短距离领域通信-32feet.NET

    32feet.NET[http://32feet.codeplex.com/]是shared-source的项目,支持CF.net 2.0以及桌面版本.NET framework,提供短距离领域(pe ...