高性能JavaScript之加载和执行
JS在浏览器中的性能,可以认为是开发者所面临的最重要的可行性问题。这个问题因JS的阻塞特性变得复杂,也就是说当浏览器在执行JS代码时,不能同时做其他任何事情。事实上,大多数浏览器都使用单一进程来处理UI(用户界面)更新和JavaScript脚本执行,所以同一时刻只能做其中一件事情。JS执行过程耗时越久,浏览器等待响应用户输入的时间就越长。
从基础层面来说,这意味着<script>标签每次出现都霸道地让页面等待脚本的解析和执行。无论当前的JS代码是内嵌的还是在外链文件中,页面的下载和渲染都必须停下来等待脚本的执行完成。这在页面生存周期中是必要的,因为脚本执行过程中可能会修改页面的内容。一个典型的例子就是在页面中使用document.write()(经常用来显示广告)。
例如:
<html> <head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Script Example</title> </head> <body>
<p>
<script>
document.write("The date is "+(new Date()).toDateString());
</script>
</p>
</body>
</html>
当浏览器遇到<script>标签时,当前的HTML页面无从获知JS是否会向<p>标签添加内容,或引入其他元素,或关闭该标签。因此,这时浏览器会停滞处理页面,先执行JS代码,然后再继续解析和渲染页面。同样的情况也发生在使用src的属性加载JS的过程中,浏览器必须先花时间下载外链文件中的代码,然后解析并执行它。在这个过程中,页面渲染和用户交互完全被阻塞了。
1.1脚本位置
这里先说说HTML4规范,HTML4规范指出<script>标签可以放在HTML文档的<head>或<body>中,并允许出现多次。按照惯例,<script>标签用来加载出现在<head>中的外链JS文件中,挨着的<link>标签用来加载外部CSS文件或其他页面元信息。也就是说,把与样式和行为有关的脚本放在一起,并先加载它们,使得页面能够显示正确的外观和交互。
例如:
<html> <head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Script Example</title>
<script src="file1.js"></script>
<script src="file2.js"></script>
<script src="file3.js"></script>
<link rel="stylesheet" type="text/css" href="style.css"> </head> <body>
<p>
Hello World
</p>
</body>
</html>
这些看似正常的代码实际上有十分严重的性能问题:在<head>中加载了三个JS文件。由于脚本会阻塞页面的渲染,直到它们全部下载并执行完成后,页面的渲染才会继续。
因此页面的性能问题会很明显。请记住,浏览器在解析到<body>标签之前,不会渲染页面的任何部分。把脚本放到页面顶部将会导致明显的延迟,通常表现为显示空白页面,用户无法浏览内容,也无法与页面进行交互。
所以通常建议像JS脚本一般都放在</body>前,也就是页面最底下,而CSS文件放在<head></head>之间。虽然说CSS文件过大也会导致延迟,但是这种延迟是可以接受的,如果是JS脚本与CSS脚本放在<head></head>之间如上面的代码所示,那样的话,延迟会显得十分明显。因此推荐<script>标签尽可能放到<body>标签底部,</body>标签之前,以尽量减少对整个页面下载的影响。
例如:
<html> <head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Script Example</title>
<link rel="stylesheet" type="text/css" href="style.css"> </head> <body>
<p>
Hello World
</p> <script src="file1.js"></script>
<script src="file2.js"></script>
<script src="file3.js"></script>
</body>
</html>
记得在《高性能网站建设》这本书,其中提到的建议之一:就是将脚本放在底部。
1.2组织脚本
由于每个<script>标签初始下载时,都会阻塞页面渲染,所以减少页面包含的<script>标签数量有助于改善这一情况。这不仅仅针对外链脚本,内嵌脚本的数量同样也要限制。浏览器在解析HTML页面的过程中每遇到一个<script>标签,都会因执行脚本而导致一定的延时,因此最小延迟时间将会明显改善页面的总体性能。
一般情况下,组织脚本不单单是将JS文件中的注释或者其他无关紧要的内容去掉,而且也要将其压缩,通过YUI或者是将多个JS文件合并压缩成一个大的JS文件。只需引用一个<script>标签,就可以减少性能的损耗(主要是减少了因加载多个脚本导致的延时)。多个合并压缩成一个大的JS文件,并将其放在CDN中并引入也是可以的。
1.3无阻塞脚本
JS倾向于阻止浏览器的某些处理过程,如HTTP请求和用户界面更新,这是开发者所面临的最显著的性能问题。减少JS文件大小并限制HTTP请求数仅仅是创建响应迅速的Web应用的第一步。Web应用的功能越丰富,所需要的JS代码就越多,所以精简源代码不总是可行的。尽管下载单个较大的JS文件只产生一个HTTP请求,却会锁死浏览器一大段时间。为了避免这种情况,你需要向页面中逐步加载JS文件,这样做在某种程度上来说不会阻塞浏览器。
无阻塞脚本的秘诀在于,在页面加载完成后才加载JS代码。用专业术语来说,这意味着在window对象中的load事件触发后再下载脚本。有多种方式可以实现这一效果。
1.3.1延迟脚本
HTML4为<script>标签定义了一个扩展属性:defer。Defer属性指明本元素所含的脚本不会修改DOM,因此代码能安全地延迟执行。该属性只有IE4和FireFox3.5+的浏览器支持,所以它不是一个理性的跨浏览器解决方案。在其他浏览器中,defer属性会被直接忽略,因此<script>标签会以默认的方式处理(即会造成阻塞)。然而,如果你的目标浏览器支持的话,这仍然是个有用的解决方案。
带有defer属性的<script>标签可以放置在文档的任何位置。对应JS文件将页面解析到<script>标签时开始下载,但并不会执行,直到DOM加载完成(onload事件被触发前)。当一个带有defer属性的JS文件下载时,它不会阻塞浏览器的其他进程,因此这类文件可以与页面中的其他资源并行下载。
任何带有defer属性的<script>元素在DOM完成加载之前都不会被执行,无论内嵌或外链脚本都是如此。
例如:
<html> <head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Script Example</title> </head> <body>
<script defer>
alert("defer");
</script> <script>
alert("script");
</script> <script>
window.onload=function(){
alert("load");
}
</script>
</body>
</html>
这段代码在页面处理过程中会弹出三次提示框。不支持defer属性的浏览器的弹出属性是"defer"、"script"、"load"。而在支持defer属性的浏览器上,弹出的顺序是:"script"、"defer"、"load"。请注意,带有defer属性的<script>元素不是跟在第二个后面执行,而是在onload事件处理器执行之前被调用。
当然了,目前我在我自己电脑上执行了上述代码,基本都不支持defer,可能需要更低的版本才能支持。
1.3.2动态脚本元素
通过文档对象模型,你几乎可以用JS动态创建HTML中的所有内容。其根本在于,<script>标签与页面中的其他元素并无差异:都能通过DOM引用,都能在文档中移动、删除、甚至被创建。用标准的DOM方法可以非常容易地创建一个新的<script>元素。
13.3XMLHttpRequest脚本注入
另外一种无阻塞加载的脚本方法是使用XMLHttpRequest对象获取脚本并注入页面中。此技术会先创建一个XHR对象,然后用它下载JS文件,最后通过创建动态<script>元素将代码注入页面中。
var xmlhttp;
if (window.XMLHttpRequest)
{// code for IE7+, Firefox, Chrome, Opera, Safari
xmlhttp=new XMLHttpRequest();
}
else
{// code for IE6, IE5
xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
}
xmlhttp.onreadystatechange=function()
{
if (xmlhttp.readyState==4 && xmlhttp.status==200)
{
document.getElementById("myDiv").innerHTML=xmlhttp.responseText;
}
}
xmlhttp.open("GET","test.js",true);
xmlhttp.send();
}
这段代码发送一个GET请求获取test.js文件。事件处理函数onReadyStateChange检查readyState是否为4,同时校验HTTP状态码是否有效(200表示有效响应,304意味着从缓存中读取)。
这种方法主要优点是:你可以下载JS代码但不立即执行。由于代码是在<script>标签之外返回的,因此它下载后不会自动执行,这使得你可以把脚本的执行推行到你准备好的时候。另一个优点是,同样的代码再所有的主流浏览器中无一例外都能正常工作。
这种方法的主要局限性是JS文件必须与所请求的页面处于相同的域,这意味着JS文件不能从CDN下载。因此大型Web应用通常不会采用XHR脚本注入。
1.3.4推荐的无阻塞模式
向页面中添加大量JS的推荐做法只需两步:先添加动态加载所需的代码,然后加载初始化页面所需的剩下的代码。因为第一部分的代码尽量精简,甚至可能只包含loadScript()函数,它下载执行都很快,所以不会对页面有太多影响。一旦初始代码就位,就用它来加载剩余的JS。
例如:
<script src="loader.js"></script>
<script>
loadScript("the-rest.js",function(){
Application.init();
});
把这段代码加载放在</body>闭合标签之前。这样做有几个好处:
(1)确保JS执行过程中不会阻碍其他内容显示;
(2)当第二个JS文件完成下载时,应用所需的所有DOM结构已经创建完毕,并做好交互准备,从而避免了需要另一个事件(比如window.onload)来检测页面是否准备好。
小结:
管理浏览器中的JS代码是个棘手的问题,因为代码执行过程中会阻塞浏览器的其他进程,比如用户界面绘制。每次遇到<script>标签,页面都必须停下了等待代码下载(如果是外链文件)并执行,然后继续处理其他部分。尽管如此,还是有几种方法能减少JS对性能的影响:
(1)</body>闭合标签之前,将所有的<script>标签放到页面底部。这能确保在脚本执行前,页面已经完成渲染;
(2)合并脚本。页面中的<script>标签越少,加载也就越快,响应也就越迅速。无论是外链还是内嵌脚本都是如此;
(3)有多种无阻塞下载JS的方法:
a.使用<script>的defer属性(注意:高版本浏览器不支持);
b.动态创建<script>元素来下载并执行代码;
c.使用XHR对象下载JS代码并注入页面中;
通过以上策略,可以极大的提高那些需要使用大量JS的Web应用的实际性能。
我的感触:
全文本质其实这么几个?
1.JS脚本放置最底下(避免延迟导致渲染效果差);
2.合并代码,将大量JS合并和压缩为一个JS文件,本质上减少HTTP请求,同时也减少并行下载带来的延迟;
做到上述两点Web应用的性能也会得到很大程度上的提升,特别是做到2,2也正说明了webpack或者gulp流行的重要原因。
高性能JavaScript之加载和执行的更多相关文章
- 高性能JavaScript(加载和执行)
当浏览器遇到 <script> 标签时,它是没办法知道 JavaScript 是否会向DOM中添加内容或引入其他元素,甚至关闭某一个标签.因此这个时候浏览器就会停止处理页面,先执行Java ...
- 浏览器中Javascript的加载和执行
在刚学习Javascript时曾对该问题在小组内做个一次StudyReport,发现其中的基础还是值得分析的. 从标题分析,可以加个Javascript的加载和执行分为两个阶段:加载.执行.而加载即浏 ...
- 浏览器环境下Javascript脚本加载与执行探析之DOMContentLoaded
在”浏览器环境下Javascript脚本加载与执行探析“系列文章的前几篇,分别针对浏览器环境下JavaScript加载与执行相关的知识点或者属性进行了探究,感兴趣的同学可以先行阅读前几篇文章,了解相关 ...
- 浏览器环境下JavaScript脚本加载与执行探析之动态脚本与Ajax脚本注入
在<浏览器环境下JavaScript脚本加载与执行探析之defer与async特性>中,我们研究了延迟脚本(defer)和异步脚本(async)的执行时机.浏览器支持情况.浏览器bug以及 ...
- 浏览器环境下JavaScript脚本加载与执行探析之defer与async特性
defer和async特性相信是很多JavaScript开发者"熟悉而又不熟悉"的两个特性,从字面上来看,二者的功能很好理解,分别是"延迟脚本"和"异 ...
- 前端性能优化:细说JavaScript的加载与执行
本文主要是从性能优化的角度来探讨JavaScript在加载与执行过程中的优化思路与实践方法,既是细说,文中在涉及原理性的地方,不免会多说几句,还望各位读者保持耐心,仔细理解,请相信,您的耐心付出一定会 ...
- 高性能JavaScript-JS脚本加载与执行对性能的影响
在web产品优化准则中,很重要的一条是针对js脚本的加载和执行方式的优化.本篇文章简单描述一下其中的优化准则. 1. 脚本加载优化 1.1 脚本位置对性能的影响 优化页面加载性能的原则之一是将scri ...
- 浏览器环境下JavaScript脚本加载与执行探析之代码执行顺序
本文主要基于向HTML页面引入JavaScript的几种方式,分析HTML中JavaScript脚本的执行顺序问题 1. 关于JavaScript脚本执行的阻塞性 JavaScript在浏览器中被解析 ...
- 高性能javascript 文件加载阻塞
高性能javascript javascript脚本执行过程中会中断页面加载,直到脚本执行完毕,此操作阻塞了页面加载,造成性能问题. 脚本位置和加载顺序:如果将脚本放在head内,那么再脚本执行完 ...
随机推荐
- 基于Keras的OpenAI-gym强化学习的车杆/FlappyBird游戏
强化学习 课程:Q-Learning强化学习(李宏毅).深度强化学习 强化学习是一种允许你创造能从环境中交互学习的AI Agent的机器学习算法,其通过试错来学习.如上图所示,大脑代表AI Agent ...
- eclipse运行jsp出现404错误怎么办?
Window/Show View/Other/Server/Servers/双击“Tomcat v7.0 Server at localhost”在Server Locations配置中选择第二个选项 ...
- JavaScript金字塔打印
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
- python之pymysql
PyMySQL 是在 Python3.x 版本中用于连接 MySQL 服务器的一个库,Python2中则使用mysqldb. 安装: pip3 install PyMySQL 常用参数: pymysq ...
- mysql中页的组成
页InnoDB采取的方式是:将数据划分为若干个页,以页作为磁盘和内存之间交互的基本单位,InnoDB中页的大小一般为 16 KB.也就是在一般情况下,一次最少从磁盘中读取16KB的内容到内存中,一次最 ...
- PL/SQL题型代码示例
1.记录类型(注意标点符号的使用) 结果: 2.学习流程 3. 4. 5. 6. 写法二: 结果: 写法三: 7.使用循环语句打印1-100 方法一: 或者 方法二: 方法三: 8. 方法二: 9. ...
- php保留两位小数并且四舍五入 保留两位小数并且不四舍五入
php保留两位小数并且四舍五入 $num = 5566.56831; echo sprintf("%.2f", $num); php保留两位小数并且不四舍五入 $num = 556 ...
- 详解thinkphp+redis+队列的实现代码
1,安装Redis,根据自己的PHP版本安装对应的redis扩展(此步骤简单的描述一下) 1.1,安装 php_igbinary.dll,php_redis.dll扩展此处需要注意你的php版本如图: ...
- 第五章 动画 48:动画-使用transition-group元素实例列表动画
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8&quo ...
- Mongodb的mapreduce
简单的看了一下mapreduce,我尝试不看详细的api去做一个group效果,结果遇到了很多问题,罗列在这里,如果别人也遇到了类似的bug,可以检索到结果. //先看person表的数据 > ...