前端性能之非阻塞加载js脚本
来源:互联网 发布:大疆没有飞行数据 编辑:程序博客网 时间:2024/06/06 06:29
SCRIPT标签的阻塞行为会对页面的性能产生影响,这是因为浏览器在下载脚本、解析、执行的过程中不会同时做其他事情,比如渲染页面、响应用户事件等。之所以这样做是因为正在执行的JavaScript代码可能会改变页面元素、修改样式、添加或者删除事件等各种操作,以及最关键的脚本之间的依赖性,浏览器必须等待当前执行的脚本执行完成之后再进行后续操作。
脚本阻塞
两种加载方式
HTML页面中的JavaScript脚本有两种方式加入
- 使用script标签内联到HTML页面,页面按从上到下的顺序执行到script标签处时执行js代码,后续HTML内容会被阻塞。
- 使用script标签的src属性将js文件从外部加载,这需要浏览器查询对应文件的缓存,如果不可用就要重新进行http请求,这样就又会产生网络延迟和下载后的解析与执行,这都会阻塞页面。
浏览器特性
一般情况下,大多数浏览器支持并行下载html、css、图片等元素,不过对于同一个域名下的资源,浏览器默认最多并行下载的个数有限制,一般是4个。另外,对于JavaScript脚本,浏览器却不支持并行下载,当下载一个脚本并解析、执行后,才能执行后一个。
浏览器之所以保证不能并行下载多个脚本,是为了防止两个有依赖的脚本在浏览器的执行顺序颠倒后,会引发变量、函数未定义的错误。同时也会更改html内容。因此,脚本必须要顺序地执行,但是这并不代表它们必须要顺序地下载。
IE8是第一个支持并行下载JavaScript脚本的浏览器,这使得页面加载多个脚本的速度加快了很多,但是这并没有解决脚本执行的阻塞问题,当A.js和B.js并行下载之后,它们依然要进行解析和顺序执行,这段时间必须要阻塞浏览器的其他行为,也就是等这段时间过了之后才能下载后续图片、css、iframe等元素。Chrome 2+和Safari 4+浏览器也是和IE8类似,会并行下载脚本但是不能
因此,最终的目的是要并行下载脚本的同时,也能下载其他元素而不会有阻塞的问题。这里都以外部脚本文件的加载来说明。
非阻塞加载外部脚本
为了避免脚本的阻塞问题,最简单的方式就是讲所有JavaScript脚本内联到HTML中,将脚本放在所有可显示元素最后面,就可以避免这个问题,但是对于大型的js文件以及缓存js文件的考虑,这个问题需要进行折中处理。针对外部的js文件请求,主要有如下几个方式进行
XHR Eval
使用XMLHTTPRequest对象从服务器异步获取js脚本文件,当响应完成后使用JavaScript语言的eval函数对响应内容进行执行。
优点:异步请求的js文件不会阻塞其他元素(图片、css等)的下载,脚本异步下载完成之后就执行。浏览器不显示”等待“。
缺点:请求的js文件必须要与主页面在同一个域名下,这对于CDN或者多域名的处理不便。不能保证执行顺序。
var xhrObj = getXHRObject();xhrObj.onreadystatechange = function(){ if(xhrObj.readyState == 4 && xhrObj.status == 200){ //依赖文件队列处理 eval(xhrObj.responseText); //后续处理 }};xhrObj.open('GET', 'a.js', true);xhrObj.send('');
一般为了保证异步加载的js文件的依赖性,需要手动保存好依赖文件队列,使其按依赖顺序执行。
XHR Injection
与XHR Eval类似,XHR Injection将异步获取的内容使用动态创建script标签的形式插入到DOM元素中去。实际测试显示使用eval方法会比这种方法的速度慢一些。
var xhrObj = getXHRObject();xhrObj.onreadystatechange = function(){ if(xhrObj.readyState == 4 && xhrObj.status == 200){ //依赖文件队列处理 var script = document.createElement('script'); document.getElementsByTagName('head')[0].appendChild(script); script.text = xhrObj.responseText; //后续处理 }};xhrObj.open('GET', 'a.js', true);xhrObj.send('');
这种方法的优缺点与XHR Eval差不多,但是速度可能会快一些。
Script in Iframe
iframe可以与主页面的其他元素并行下载,并且不会阻塞。但是iframe是为了包含其他的HTML页面,其他的HTML页面也可以包含js脚本,因此可以将需要加载的js脚本放入一个html文件中然后使用iframe非阻塞加载这个html文件即可。
优点:异步加载脚本,浏览器支持较好
缺点:需要与主页面同一个域名,需要将外部的js文件转换为html从而作为iframe的src属性。另外,iframe是一个非常重量级的DOM元素。不能保证执行顺序。浏览器会显示”等待“。
iframe本身也都可以使用js脚本进行动态创建:
var _ = function(d){document.getElementById(d);};var removeNode = function(a) { try { typeof a == "string" && (a = _(a)); a.parentNode.removeChild(a) } catch (b) {}};var addEvent = function(ele, event, call){ ele.addEventListener ? ele.addEventListener(event, call, !1) : a.attachEvent ? a.attachEvent("on" + event, call) : a["on" + event] = call;};var loadScriptByIframe = function(id, src){ src == null && (src = "javascript:false;"); removeNode(id); var c = document.createElement('iframe'); c.height = 0; c.width = 0; c.style.display = "none"; c.name = id; c.id = id; c.src = src; c.isReady = !1; addEvent(c, "load", function(){ if(! c.isReady){ c.isReady = !0; //当前脚本已加载完成,执行其他js代码 } }); document.body.appendChild(c); window.frames[id].name = id; return c;};
在iframe和主页面中的脚本需要进行修改才能互相访问:
- 从主页面中访问iframe
window.frames[0].methodInIframe(); //使用framesdocument.getElementById('iframeID').contentWindow.methodInIframe(); //使用getElementById
- 从iframe访问主页面
function methodInIframe(){ var newDiv = parent.document.createElement('div'); parent.document.body.appendChild(newDiv);}
Script DOM Element
这个方法是使用最多的方法,通过js脚本动态创建script标签插入到DOM中,可以动态设置src属性,并且可以是不同域名下的js文件,一般百度统计、cnzz、google analysis等网站统计工具提供的统计代码就是使用这种方式,是最为人知使用最为广泛的方法。
优点:可以跨域,创建script元素不会阻塞其他元素的下载,并且代码实现简单方便。
缺点:下载后的脚本并不能保证按顺序执行,仅仅在FireFox浏览器下保证顺序执行,其他浏览器存在脚本文件的依赖冲突。浏览器显示”等待“。
var loadScriptByScriptDom(id, src, c){ removeNode(id); var d = document.getElementsByTagName('head')[0], e = document.createElement('script'); e.charset = c || 'utf-8'; e.id = id; e.type = 'text/javascript'; e.src = src; d.appendChild(e);}
Script Defer
IE浏览器的script标签支持defer属性,当使用这个属性之后,浏览器会识别,表示不会立即下载这个script标签对于的js文件,当这个js文件中没有document.write调用,并且没有其他脚本依赖这个文件,那么就可以使用。IE在下载这个文件时不会阻塞其他元素的下载。
优点:实现方便,保证js文件执行的顺序,支持跨域
缺点:浏览器会显示”等待”状态,仅仅支持defer属性的IE浏览器适用。
<script defer src="a.js"></script>
document.write Script tag
JavaScript语言中可以调用document的write方法将内容写入到html的DOM中,这种方式仅仅在IE中并行下载,并且在下载的过程中其他资源依然会被阻塞。并不是一种好的解决方式。
document.write("<script type='text/javascript' src='a.js'></script>")
评价标准
浏览器”等待“标志
浏览器等待的标志包括:状态栏、进度条、tab图标、光标形状,另外还有阻塞的渲染和阻塞的onload事件,前者是在当前下载的js脚本之后的可视内容都被阻塞从而不会渲染,后者是只有所有资源都下载完成呈现页面之后这个事件才会触发。
绝大多数浏览器的”等待”状态都会被使用script的src方式加载的过程中被触发,因此会被用户感知到页面还未完成加载。但是这些状态标识不会被基于XHR的XHR Eval和XHR Injection方法触发。
多个js脚本的执行顺序
当使用上述的方法加载多个js脚本时,如果他们之间有依赖关系,那么必须保证其执行顺序不变,否则会出现各种未定义的错误。大多数情况下,这不仅与方法有关,也与浏览器有关,使用script 的src属性的方法都保证执行顺序与页面中排布的顺序一致。
对于IE浏览器,script defer和document.write script tag方法保证其执行顺序与排布顺序一致。
FireFox浏览器,不支持script defer属性,同时document.write方法也不能进行并行下载。但是script DOM方法可以在FireFox浏览器中按照其在页面中的排布顺序来执行,但是其他浏览器却不行。
上述基于XHR的方法是不能保证执行的顺序的,但是可以通过下载队列的手动处理后来保证其执行顺序。
结论
根据上述多方面的评价,document.write方法不仅对浏览器的依赖很严重,自身也依然会阻塞后续资源,因此应该避免使用,其他方式针对不同浏览器情况,以及是否需要保证执行顺序和是否显示浏览器的“等待”标志,需要区别对待才行。最终版本如下:
OS = function(){ var dom = document, me = this; this.addEvent = function(ele, event, call){ ele.addEventListener ? ele.addEventListener(event, call, !1) : a.attachEvent ? a.attachEvent("on" + event, call) : a["on" + event] = call; }; this.getXHRObject = function(){ var xhrObj = false; try{ xhrObj = new XMLHttpRequest(); }catch(e){ var msTypes = ["Msxml2.XMLHTTP.6.0", "Msxml2.XMLHTTP.3.0", "Msxml2.XMLHTTP", "Microsoft.XMLHTTP"]; var l = msTypes.length; for(var i = 0; i < l; i++){ try{ xhrObj = new ActiveXObject(msTypes[i]); }catch(e){ continue; } break; } }finally{ return xhrObj; } }; this.LoadScript = { Default : function(url, onload){ me.LoadScript .DomElement(url, onload); }, DomElement : function(url, onload){ var domscript = dom.createElement('script'); domscript.src = url; if (onload){ domscript.onloadDone = false; domscript.onload = onload; domscript.onreadystatechange = function(){ if ("loaded" === domscript.readyState && domscript.onloadDone){ domscript.onloadDone = true; domscript.onload(); } } } dom.getElementsByTagName('head')[0].appendChild(domscript); }, DocWrite : function(url, onload){ document.write('<scr' + 'ipt src="' + url + '" type=text/javascript"></scr' + 'ipt>'); if (onload){ me.addEvent(window, "load", onload); } }, queuedScripts : new Array(), XhrInjection : function(url, onload, isOredered){ var q = me.LoadScript.queuedScripts.length; if (isOredered){ var qScript = {response: null, onload:onload, done: false}; me.LoadScript.queuedScripts[q] = qScript; } var xhrObj = me.getXHRObject(); xhrObj.onreadystatechange = function(){ if (xhrObj.readyState == 4){ if (isOrdered){ me.LoadScript.queuedScripts[q].response = xhrObj.responseText; me.LoadScript.injectScripts(); }else{ eval(xhrObj.responseText); if(onload) onload(); } } }; xhrObj.open('GET', url, true); xhrObj.send(''); }, injectScripts : function(){ var queue = me.LoadScript.queuedScripts; var len = queue.length; for (var i = 0; i < len; i++){ var qScript = queue[i]; if( ! qScript.done ){ break; }else{ eval(qScript.response); if (qScript.onload){ qScript.onload(); } qScript.done = true; } } }, };};//end of OS
- 前端性能之非阻塞加载js脚本
- 非阻塞JavaScript脚本加载
- 非阻塞JavaScript脚本加载【优化网站】
- 浏览器加载js的阻塞与非阻塞
- 浏览器加载js的阻塞与非阻塞
- 前端性能优化之-css阻塞渲染
- JS脚本文件的位置对页面加载性能影响以及无阻塞脚本(javascript)模式
- 前端性能之懒加载
- js之Event Loop?阻塞模式/非阻塞模式
- 高性能Javascript--脚本的无阻塞加载策略
- 高性能Javascript:脚本的无阻塞加载策略
- 高性能Javascript--脚本的无阻塞加载策略
- 高性能Javascript:脚本的无阻塞加载策略
- 高性能Javascript--脚本的无阻塞加载策略
- 高性能Javascript--脚本的无阻塞加载策略
- 高性能Javascript:脚本的无阻塞加载策略
- 高性能JavaScript:脚本的无阻塞加载策略
- 高性能Javascript:脚本的无阻塞加载策略
- centos ffmpeg m3u8切片相关
- post 和get提交的乱码问题
- java-常量和变量
- LeetCode SubSets
- Power Calculus(POJ 3134)
- 前端性能之非阻塞加载js脚本
- Android侧滑抽屉效果实现
- webrtc 支持h264 思路
- POJ-1328-Radar Installation
- 字符串各种数据类型
- opencv 其他形态学变换
- OpenSSL命令行工具的证书操作
- HDU 2665 Kth number
- 百度地图定位纠偏