JS在浏览器中的性能,能够认为是开发者所面临的最重要的可行性问题。这个问题因JS的阻塞特性变得复杂,也就是说当浏览器在执行JS代码时,不能同时作其余任何事情。事实上,大多数浏览器都使用单一进程来处理UI(用户界面)更新和JavaScript脚本执行,因此同一时刻只能作其中一件事情。JS执行过程耗时越久,浏览器等待响应用户输入的时间就越长。css
从基础层面来讲,这意味着<script>标签每次出现都霸道地让页面等待脚本的解析和执行。不管当前的JS代码是内嵌的仍是在外链文件中,页面的下载和渲染都必须停下来等待脚本的执行完成。这在页面生存周期中是必要的,由于脚本执行过程当中可能会修改页面的内容。一个典型的例子就是在页面中使用document.write()(常常用来显示广告)。html
例如:webpack
<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的过程当中,浏览器必须先花时间下载外链文件中的代码,而后解析并执行它。在这个过程当中,页面渲染和用户交互彻底被阻塞了。web
1.1脚本位置gulp
这里先说说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流行的重要缘由。