高性能JavaScript-JS脚本加载与执行对性能的影响
在web产品优化准则中,很重要的一条是针对js脚本的加载和执行方式的优化。本篇文章简单描述一下其中的优化准则。
1. 脚本加载优化
1.1 脚本位置对性能的影响
优化页面加载性能的原则之一是将script标签放在body底部,这跟浏览器的渲染原理有关:
- js脚本的下载和执行会阻塞浏览器的解析。在较早时期,浏览器不支持并行下载的时候,js脚本的下载执行按照在html文档中的位置依次进行,可以想象当页面有大量js脚本时页面的加载有多慢;
- js脚本的下载会阻塞其他资源的下载,比如图片、外链css等。虽然目前大多数浏览器支持并行下载,外链js文件可以并行下载,但是在下载js的过程中,其他资源的加载仍然会被阻塞。
综上所述,除非业务需求必须将js脚本放在指定位置,最佳的优化准则是将js放于body底部。
1.2 合并脚本文件
每个script标签都会阻塞页面的解析和其他资源的加载,可以通过合并js脚本文件进行优化。虽然目前大多数浏览器支持并行下载,但是随着web产品越来越庞大,浏览器支持的并行数量明显不能满足需求,随意js文件的合并打包是很有必要的。
目前较流行的grunt/gulp/webpack等编译工具都支持文件的打包合并,webpack甚至可以将css文件也一并打包到js文件里。我们先不去评价这种模式的好坏,单从减少文件数量这个角度来看,这是为了减少http请求数目、script标签数量以提高页面的加载性能。
其实这种理念很早就有,有后端开发经验的朋友可能接触过combo handler,这是Yahoo YUI团队开发的一个Apacha模块。combo handler可以支持浏览器使用一个url请求多个文件,比如我们页面中需要两个js文件,常规情况下使用2个script标签请求:
<script src='http://static.me.com/a.js'></script>
<script src='http://static.me.com/b.js'></script>
使用combo handler可以通过以下形式:
<script src='http://static.me.com?a.js&b.js'></script>
a.js和b.js在服务器是独立存在的,combo handler可以通过一个http请求将两者合并为一起返回,减少了http请求数目,提高了页面加载性能。
2. 无阻塞脚本
2.1 defer和async
defer和async都是针对外链的js脚本文件,如下:
<script src='http://static.me.com/a.js' defer></script>
<script src='http://static.me.com/a.js' async></script>
defer和async的作用都是令指定的js文件异步加载,不影响html文档其他内容的解析,也就是说带有defer和async的js文件和html文档的解析是并行的。但是两者的运行机制有稍许差别。
defer在IE4就引入了,目前几乎所有浏览器都支持。defer的js文件在并行下载结束后并不立即执行,其执行时机是在文档加载完毕后window.onload触发之前。
async是HTML5引入的新规范,目前获得了大多数浏览器的支持。async的js文件在并行下载结束后立即执行。
比较defer和async的区别可以得到以下结论:
- 两者都是并行下载,不影响html文档的解析;
- defer文件的执行时机是在window.onload之前,所以defer文件的位置任意;
- async文件下载结束后立即执行,是乱序的。所以并不适用于有依赖关系的js脚本;
- defer和async的脚本中应当避免使用
document.wirte
,否则会清空页面原有的内容。
2.2 动态脚本
所谓动态脚本的意思是使用JavaScript创建一个script,指定其src并将此script标签插入文档的head中。
之所以将动态script标签插入head而不是body中是因为在低版本IE中如果在html文档未解析完毕时,body中插入script标签会抛出“操作已终止”的错误信息。具体可参考The dreaded Operation Aborted error。
动态脚本请求到的js脚本是立即执行的。
动态创建script标签时,某些业务场景下需要监听被请求的js脚本是否加载完毕。大多数浏览器都支持script.onload
事件:
script.onload = function(){}
IE浏览器没有实现onload事件,而是会触发readystatechange
事件。当readyState
的状态为loaded
或complete
时便可以认为js脚本文件已加载完毕。兼容所有浏览器的loadScript函数如下:
function loadScript(url,callbakc){
var script = document.createElement('script');
script.type = 'text/javascript'; //低版本IE下必须制定type,其他浏览器可以不写
if(script.readtState){ //IE
script.onreadystatechange = function(){
if(script.readyState === 'loaded' || script.readyState === 'complete'){
script.readyState === 'null';
callback();
}
};
}else{ //其他浏览器
script.onload = function(){
callbakc();
}
}
script.src = url;
document.getElementByTagName('head')[0].appendChild(script);
}
2.3 使用XHR注入js脚本
使用XHR注入脚本是比较偏门并且应用面很小的一门技术,原理就是用Ajax去get请求一个js文件,监听xhr.status
,获取到的响应信息是js文件的代码。然后动态创建一个script标签,将获取到的js代码注入script标签内,最后将script标签插入文档中。
这种方式注入的js脚步并不会立即执行。缺点是无法跨域,因此很少得到应用。
- 【插件开发】—— 14 Site is incorrect!编辑器启动报错!
- Java魔法堂:Date与日期时间格式化
- Java魔法堂:打包知识点之META-INF/MAINFEST.MF
- WordPress快速建站
- 大数据时代下的生活
- 【Spring实战】—— 1 入门讲解
- 博客园小技巧
- JS魔法堂:关于元素位置和鼠标位置的属性
- MyBatis魔法堂:Insert操作详解(返回主键、批量插入)
- Winodws安装系统时,通过安装磁盘进行分区
- Eclipse安装SVN插件
- JS魔法堂:IE5~9的Drag&Drop API
- mysql 5.7版本目录无data文件夹的解决办法
- 【UML】——为什么要使用UML
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法