关于js的执行与加载
Posted 小艾想偷懒
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了关于js的执行与加载相关的知识,希望对你有一定的参考价值。
js在浏览器中性能,可以认为是开发者所面临的最严重的可用性问题了,这个问题因为js的阻塞特性变得很复杂,也就是说浏览器在执行js代码时,不能同时做其他任何事情。事实上,多数浏览器使用单一进程来处理用户界面刷新和js脚本的执行,所以只能同一时刻做一件事,js的执行过程耗时越久,浏览器等待响应的时间就越长。
简单的说,这意味着<script>标签每次出现都霸道地让页面等待脚本的解析和执行。无论当前的js代码时内嵌还是外链接,页面的下载和渲染都必须停下来等脚本的执行完成。这是页面生存周期中的必要环节,因为脚本执行过程中可能会修改页面内容。一个典型例子就死document.write().我们看到的广告就是这么搞的。
脚本的位置
html4规范指出<script>标签可以放在html文档的<head>或<body>中,并允许出现多次。按照惯例,<script>标签用来加载出现在css加载的<link>标签后。理论上来说,把样式和行为有关的脚本放在一起,并先加载它们,这样做有助于页面的渲染和交互的正确性。
但是,这样存在十分严重的性能问题,在<head>标签中加载js文件,由于脚本会阻塞页面的渲染,直到它们全部下载并执行完成后,页面的渲染的才会执行。要知道,浏览器在解析到<body>标签之前,不会渲染页面的任何内容,把脚本放在页面顶部会导致明显的延迟,会有明显的白屏时间,用户无法浏览内容,也无法与页面进行交互。瀑布图可以帮我们更清楚地理解性能发生的原因。因此js要放在<body>标签的底部。
组织脚本
每一个<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代码并注入页面中。
以上是关于关于js的执行与加载的主要内容,如果未能解决你的问题,请参考以下文章
关于Oppo、Vivo手机低于Android 7.0版本WebView中JS不执行,CSS加载异常的问题