关于js的执行与加载

js在浏览器中性能,能够认为是开发者所面临的最严重的可用性问题了,这个问题由于js的阻塞特性变得很复杂,也就是说浏览器在执行js代码时,不能同时作其余任何事情。事实上,多数浏览器使用单一进程来处理用户界面刷新和js脚本的执行,因此只能同一时刻作一件事,js的执行过程耗时越久,浏览器等待响应的时间就越长。javascript

简单的说,这意味着<script>标签每次出现都霸道地让页面等待脚本的解析和执行。不管当前的js代码时内嵌仍是外连接,页面的下载和渲染都必须停下来等脚本的执行完成。这是页面生存周期中的必要环节,由于脚本执行过程当中可能会修改页面内容。一个典型例子就死document.write().咱们看到的广告就是这么搞的。css

 

脚本的位置html

  html4规范指出<script>标签能够放在html文档的<head>或<body>中,并容许出现屡次。按照惯例,<script>标签用来加载出如今css加载的<link>标签后。理论上来讲,把样式和行为有关的脚本放在一块儿,并先加载它们,这样作有助于页面的渲染和交互的正确性。java

可是,这样存在十分严重的性能问题,在<head>标签中加载js文件,因为脚本会阻塞页面的渲染,直到它们所有下载并执行完成后,页面的渲染的才会执行。要知道,浏览器在解析到<body>标签以前,不会渲染页面的任何内容,把脚本放在页面顶部会致使明显的延迟,会有明显的白屏时间,用户没法浏览内容,也没法与页面进行交互。瀑布图能够帮咱们更清楚地理解性能发生的缘由。所以js要放在<body>标签的底部。web

 

 

组织脚本gulp

    每个<script>标签初始下载时都会阻塞页面渲染,因此减小页面包含的<script>标签数量有助于改善这一状况,这不只仅是针对外链脚本,内链脚本的数量也要限制,这个问题在处理外链脚本文件时略有不一样,由于http请求还会带来额外的性能开销,所以下载单个100kb的文件将比下载四个25kb的文件更快,也就是说,减小页面中脚本文件数量将会改善性能。浏览器

一般一个大型网站或网络应用须要依赖数个js文件,咱们能够把多个文件合并成一个,这样就只需引用一个<script>标签了。文件合并能够利用如今的不少构建工具,grunt,gulp等,都很方便。缓存

无阻塞的脚本安全

     js倾向于阻止浏览器的某些处理过程,如http请求和用户界面更新,这是开发者所面临的最显著的性能问题。减小js文件大小并限制http请求仅仅是建立响应迅速的Web应用的第一步,web应用的功能愈来愈强大丰富,所须要的脚本代码也就越多,因此精简代码并不老是可行,尽管下载单个较大的js文件只产生一次http请求,却会锁死浏览器一大段时间,这样显然不是良好的用户体验,为避免这种状况,咱们须要的是向页面中逐步加载js文件,这样作从某种程度上不会阻塞浏览器。服务器

无阻塞脚本的秘诀在于,在页面加载完后才加载js代码,用专业术语说,这意味着window对象的load事件触发后再下载脚本,有不少方式能够实现这一效果。

 

《1》延迟的脚本

      html4为<script>标签订义了一个扩展属性,defer。defer属性指明本元素所含的脚本不会修改dom,所以代码能够安全的延迟执行。这个属性目前已经被全部的主流浏览器支持了。另外说说HTML5 中引入的async属性,用于异步加载脚本。async和defer的相同点是采用并行下载,在下载的过程不会产生阻塞,区别在于执行的时机,async是加载完成后自动执行,而defer须要等待页面完成后才执行。

带有defer属性的<script>标签能够放置在文档的任何位置,对应的js文件将在解析到<script>标签时开始下载,但不会执行,直到dom加载完成后(onload事件被触发前)所以这类文件能够与页面中的其余资源并行下载。

<script type='type/javascript ' src="xiaoai.js" defer></script>

示例:

<html>

<head>

<title> script defer</title>

</head>

<body>

<script defer>

alert(1);

</script>

<script>

alert(2);

</script>

<script>

window.onload=function(){

alert(3);

}

</script>

</body>

</html>

这段代码弹出三次提示框,若你的浏览器支持defer,弹出的顺序为2,1,3;而不支持defer的的浏览器则是1,2,3。请注意,带有defer属性的浏览器不是跟在第二个执行,而是在onload事件以前执行。

 

《2》动态脚本

   因为DOM的存在,你能够用js建立HTML中几乎全部内容。其缘由在于,<script>元素与页面其余元素并没有差别:都能经过DOM进行引用,都能在文档中移动,删除或是被建立。用标准的DOM方法能够很容易的建立一个新的<script>元素:

var script=document.createElement(‘script’);

script.type="text/javascript";

script.src="file1.js";

document.getElementsByTagName('head')[0].appendChild(script);

   这个新建立的<script>元素加载了file1.js文件。文件在该元素被添加到页面时开始下载。这种技术的重点在于:不管在什么时候启动下载,文件的下载和执行过程不会阻塞页面的其余进程。你甚至能够将代码放到页面<head>区域而不会影响页面其余部分(用于下载文件的http连接自己的影响除外)。

 另外,要注意,把新建立的<script>标签添加到<head>标签里比添加到<body>里更保险,尤为是在页面加载过程当中执行代码时更是如此。当<body>中的内容没有加载完成时,IE会抛出“操做已终止”的错误信息。

使用动态脚本节点下载文件时,返回的代码一般会当即执行(除了Firefox和opera,它们会等待此前全部动态脚本节点执行完毕)。当脚本‘自执行’时,这种机制运行正常。可是当代码只包含供页面其余脚本调用的接口时,就会有问题。在这种状况下,你必须跟踪并确保脚本下载完成且准备就绪。这能够用动态<script>节点触发的事件来实现。

Firefox,opera,Chrome和Safari以上的版本会在<script>元素接收完成时触发一个load事件。所以能够经过侦听此事件来得到脚本加载完成时的状态;

var script=document.createElement('script')

script.type='text/javascript';

script.onload=function(){

alert("script loaded");

};

script.src='file2.js';

document.getElementByTagName('head')[0].appendChild(script);

IE支持另外一种实现方式,它会触发一个readyStatechange事件。<script>元素提供一个readyState属性,它的值在外链文件的下载过程的不一样阶段会发生变化,该属性有五种取值:

“uninitialized”   初始状态

“loading” 开始下载

“loaded” 下载完成

“interactive” 数据完成下载但尚不可用

“complete” 全部数据已准备就绪

微软的相关文档代表,<script>元素生命周期中,并不是readyState的每一个取值都会被用到,实际应用中,最有用的两个状态就是“loaded”和“complete”。Ie在标识最终状态时的值并不一致,有时<script>元素达到“loaded”状态而从不会到达“complete”,有时候直接跳到“complete”而不通过“loaded”,使用这个属性时最靠谱的方式是同时检查这两个状态,只要其中任何一个触发,就删除事件处理器(以确保不会处理两次)。

 var script=document.createElement('script')

script.type="text/javascript";

script.onreadystatechange=function(){

if(script.readyState=="loaded"||script.readyState=="complete"){

script.onreadystatechange=null;

alert('script loaded');

};

script.src='file3.js';

document.getElementsByTagName('head')[0].appendChild(script);

}

以上是针对IE的动态加载js文件方法。

咱们须要一个兼容各浏览器的动态加载js文件的方法,下面是一个函数封装了标准和IE特有的实现方法

function loadscript(url,callback)

{

var script=document.createElement('script')

script.type='text/javascript';

if(script.readyState){//IE

script.onreadystatechange=function(){

if(script.readyState=="loaded"||script.readystate=="complete"){

script.onreadystatechange=null;

callback();

}else{//其余浏览器

script.onload=function()

{

callback();

};

}

script.src=url;

document.getElementsByTagName('head')[0].appendChild(script);

}

这个函数接收两个参数:JavaScript文件的URL和完成加载后的回调函数。函数中使用了特征检测来决定脚本处理过程当中监听哪一个事件。最后一步是给src属性赋值,而后将<script>元素添加到页面。loadscript()函数用法以下

loadscript("file1.js",function(){

alert(‘file is loaded’);

});

若是须要的话,你能够动态加载尽肯能多的jswenjian 到页面上,但必定要考虑清楚文件的加载顺序。在全部的主流浏览器中,只有Firefox和opera能保证脚本会按照你指定的顺序执行,其余浏览器会按照从服务端返回的顺序下载和执行代码。所以能够经过下面的串联方式以确保下载顺序。

loadscript(‘file1.js’,function(){

loadscript('file2.js',function(){

loadscript("file3.js",function(){

alert('all file is loaded');

});

});

});

下载顺序为 file1,file2,file3。

若是多个文件的下载顺序很重要,更好的作法是把他们按正确的顺序合并成一个文件。下载这个文件就会得到全部的代码(因为这个过程是异步的,所以文件大点不要紧)

总而言之,动态脚本加载凭借着它在跨浏览器兼容性和易用的优点,成为最通用的无阻塞加载js的解决方案。

《3》XMLhttpRequest 脚本注入

另外一种无阻塞加载脚本的方法是使用XMLHttpRequest(XHR)对象获取脚本并注入页面中。

此技术胡建立一个XHR对象,而后用它下载JavaScript文件,最后经过建立动态<script>元素将代码注入到页面中。

var xhr =new XMLHttpRequest();

xhr.open('get','file1.js',true);

xhr.onreadystatechange=funcition(){

if(xhr.readyState==4){

if(xhr.status>=200&&xhr.status<300||xhr.status==304){

var script=document.creat.createElement('script');

script.type="text/javascript";

script.text=xhr.responseText;

document.body.appendChild(script);

}

}

};

chr.send(null);

这段代码发送一个GET请求获取file.js文件。事件处理函数onreadychange检查readyState是否为4,同时检验http状态码是否有效(2xx表明有效响应,304表明从缓存中读取)。若是收到了有效响应,就会建立一个<script>元素,设置该元素的text属性为从服务器接收到的resposeText。这样其实是建立一个带有内联脚本的<script>标签。一旦新建立的<script>元素被添加到页面,代码就会马上执行而后准备就绪。

这种方法的优势是:你能够下载JavaScript代码但不当即执行。因为代码是在<script>标签以外返回的,所以它下载后不会自动执行,这使得你能够把脚本执行推迟到你准备好的时候。另外一个优势是,一样的代码在全部浏览器都能正常工做。

这种方法的局限性是:js文件必须与所请求的页面处于相同的域,这意味着js文件不能从cdn下载。所以,大型的web应用一般不会采用XHR脚本注入技术。

 

小结:

   管理浏览器中js代码是个棘手的问题,由于代码执行过程会阻塞浏览器的其余进程,好比用户界面绘制。每次遇到<script>标签,页面都必须停下来等待全部的js代码下载并执行,而后恢复处理。尽管如此,仍是有几种方法能减小js对性能的影响:

1.<body>闭合标签以前,将全部<script>标签放在页面底部。这能确保脚本执行前页面已经完成渲染。

2.合并脚本。页面中<script>标签越少,加载也就越快,响应也更迅速。不管外链文件仍是内嵌的脚本都是如此。

3.有多种无阻塞下载js的方法:

---<script defer>

---使用动态建立的<script>元素来下载并执行代码

---使用XHR对象下载js代码并注入页面中。

相关文章
相关标签/搜索