JavaScript阻塞剖析与改善
| 一、阻塞特性 |
《高性能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);
| 三、拓展阅读 |
[2] HTML渲染过程详解
[3] 浏览器加载渲染网页过程解析
[4] defer、async属性以及JS异步加载并执行解决方案
[5] HTML5 <script>元素async,defer异步加载
JavaScript阻塞剖析与改善的更多相关文章
- javascript prototype 剖析
学过javascript的一定对prototype不陌生,但是这个究竟是个什么东西,就不一定很清楚. 我们先对prototype进行一个定义:每个函数都有一个prototype属性,这个属性是指向一个 ...
- js1:对象的学习,构造函数,继承构造函数【使用教材:JavaScript深度剖析第2版】
原文发布时间为:2008-11-08 -- 来源于本人的百度文章 [由搬家工具导入] <html> <head> <title>js</title> & ...
- 探真无阻塞加载javascript脚本技术,我们会发现很多意想不到的秘密
下面的图片是我使用firefox和chrome浏览百度首页时候记录的http请求 下面是firefox: 下面是chrome: 在浏览百度首页前我都将浏览器的缓存全部清理掉,让这个场景最接近第一次访问 ...
- 更优雅的方式: JavaScript 中顺序执行异步函数
火于异步 1995年,当时最流行的浏览器--网景中开始运行 JavaScript (最初称为 LiveScript). 1996年,微软发布了 JScript 兼容 JavaScript.随着网景.微 ...
- JavaScript 如何工作:渲染引擎和性能优化技巧
翻译自:How JavaScript works: the rendering engine and tips to optimize its performance 这是探索 JavaScript ...
- 前端性能优化:细说JavaScript的加载与执行
本文主要是从性能优化的角度来探讨JavaScript在加载与执行过程中的优化思路与实践方法,既是细说,文中在涉及原理性的地方,不免会多说几句,还望各位读者保持耐心,仔细理解,请相信,您的耐心付出一定会 ...
- JavaScript学习(零)前引
一)概述 JavaScript是一个面向web的编程语言,一种解释性语言,边执行边解释.也是一种基于对象(Object)和事件驱动(EventDriven)的,安全性好的脚本语言,语法和java类似. ...
- 以优美方式编写JavaScript代码
英文原文:CoffeeScript: The beautiful way to write JavaScript 我用 JavaScript 编程很多年了,写了大量的 JavaScript 代码,即便 ...
- 再谈DOMContentLoaded与渲染阻塞—分析html页面事件与资源加载
浏览器的多线程中,有的线程负责加载资源,有的线程负责执行脚本,有的线程负责渲染界面,有的线程负责轮询.监听用户事件. 这些线程,根据浏览器自身特点以及web标准等等,有的会被浏览器特意的阻塞.两个很明 ...
随机推荐
- C语言与java 20155317 王新玮第二次
20155317 王新玮第二次写作感想 你有什么技能比大多数人(超过90%以上)更好? 刚刚看到这个题目,我的首先想到的是会一些中医,懂得中医的理论框架知识,懂得大部分的中医脉象,能够解决日常生活 ...
- Django models知识小点
django 为使用一种新的方式,即关系对象映射(ORM) 一,创建表 1,基本结构 注意: 1,创建标的时候,如果我们不给表加自增列,生成表的时候会默认给我们生成一列为ID的自增列,当然我们也可以自 ...
- Allegro之测量时显示两种单位(mil & mm)
首先确认你工程默认的单位是mil还是mm 例子为mils 在下面的选项中选择另外一项mm即可(如本身是mm,这里选择mils) 点击Apply,然后OK,操作测量功能,如下图所以效果:
- linux下Vim的使用
在vim中移动光标跟其他的编辑器中有很大的区别,不过一旦学会了,就会飞速的在文本中移动 复制粘贴dd 删除光标所在行dw 删除一个字(word)x 删除当前字符X 删除前一个字符D 删除到行末yy 复 ...
- Java实现文件在某个目录的检索
package com.filetest; import java.io.File; import java.util.Date; import java.util.Scanner; public c ...
- SQL Server最近怎样了
SQL Server最近怎样了 又到年终了,大家都作最后冲刺 最近园子里真的多了很多口水帖,无论大家争论得多么激烈,时间依然滴答滴答地过,争论完之后我们依然要继续埋头苦干 为年终奖.为明年做准备 这里 ...
- ABP理论学习之发布说明
返回总目录 查看更详细信息以及下载源代码请查看原文档 ABP v0.9.2.0 | [更新日期:2016/6/6 11:21:28 ] 解决方案转换成xproj/project.json格式. 添加了 ...
- ENode框架Conference案例分析系列之 - 业务简介
前言 ENode是一个应用开发框架.通过ENode,我们可以方便的开发基于DDD+CQRS+EventSourcing+EDA架构的应用程序.之前我已经写了很多关于ENode的架构以及设计原理的文章, ...
- SQL Server 复制订阅
标签:SQL SERVER/MSSQL SERVER/数据库/DBA/高性能解决方案/高可用 概述 配置复制就没有数据库镜像和AlwaysOn的要求那么高,只需要两台服务器能通过TCP进行通讯即可,两 ...
- C语言 · Interval · 求矩阵元素和
问题描述 这里写问题描述. 输入格式 测试数据的输入一定会满足的格式. 例:输入的第一行包含两个整数n, m,分别表示矩阵的行数和列数.接下来n行,每行m个正整数,表示输入的矩阵. 输出格式 要求用户 ...