javascript
JS 实现 jQuery的$(function(){});
1、瀏覽器渲染引擎的HTML解析流程
何謂“渲染”,其實(shí)就是瀏覽器把請(qǐng)求到的HTML內(nèi)容顯示出來(lái)的過(guò)程。渲染引擎首先通過(guò)網(wǎng)絡(luò)獲得所請(qǐng)求文檔的內(nèi)容,通常以8K分塊的方式完成。下面是渲染引擎在取得內(nèi)容之后的基本流程:
1,解析html以構(gòu)建dom樹(shù)(構(gòu)建DOM節(jié)點(diǎn)):渲染引擎開(kāi)始解析html,并將標(biāo)簽轉(zhuǎn)化為內(nèi)容樹(shù)中的dom節(jié)點(diǎn)。
2,構(gòu)建render樹(shù)(解析樣式信息):解析外部CSS文件及style標(biāo)簽中的樣式信息。Render樹(shù)由一些包含有各種屬性的矩形組成,它們將被按照正確的順序顯示到屏幕上。
3,布局render樹(shù)(布局DOM節(jié)點(diǎn)):執(zhí)行布局過(guò)程,它將確定每個(gè)節(jié)點(diǎn)在屏幕上的確切坐標(biāo)。
4,繪制render樹(shù)(繪制DOM節(jié)點(diǎn)):Render樹(shù)構(gòu)建好了之后,將會(huì)再下一步就是繪制,即遍歷render樹(shù),并使用UI后端層繪制每個(gè)節(jié)點(diǎn)。
以上就是HTML渲染的基本流程,但這并不包含解析過(guò)程中瀏覽器加載外部資源如圖片、腳本、iframe等的過(guò)程。說(shuō)白了,上面的四步僅僅是HTML結(jié)構(gòu)的渲染流程,而外部資源的加載在HTML結(jié)構(gòu)的渲染流程中貫穿始終,即便繪制DOM節(jié)點(diǎn)已經(jīng)完成,外部資源依然可能正在加載中或尚未加載。
?
2、window.onload
了解了瀏覽器渲染引擎的HTML解析流程,我們就回到domReady。前文提到了,那個(gè)蛋疼的TypeError是由于在DOM樹(shù)構(gòu)建完成之前對(duì)節(jié)點(diǎn)進(jìn)行了操作,而通常的解決的辦法就是讓js在window.onload的回調(diào)里執(zhí)行,也就是說(shuō),在文檔所有的解析渲染、資源加載完成之前,不讓js腳本執(zhí)行,這樣一來(lái)就妥妥地避免了因js操作先于DOM樹(shù)創(chuàng)建而帶來(lái)的bug:
1 Window.onload = function(){ 2 //doSomething 3 }這樣的解決辦法應(yīng)該是初學(xué)原生js時(shí)很多人最常用的解決辦法,看起來(lái)也的確沒(méi)什么問(wèn)題。如果文檔外部資源不多的時(shí)候也沒(méi)什么問(wèn)題,但,我們來(lái)做一個(gè)假設(shè)。假設(shè)一個(gè)頁(yè)面上有100張遠(yuǎn)程圖片,我需要讓js做到在點(diǎn)擊每張圖片時(shí)alert出圖片的src屬性,又該怎么做?
是不是已經(jīng)發(fā)現(xiàn)點(diǎn)小問(wèn)題了?按照第二部分內(nèi)容對(duì)瀏覽器解析渲染HTML流程的介紹,DOM樹(shù)很快就構(gòu)建完畢了,而100張圖片還在緩慢地加載。而要想執(zhí)行alert出圖片src屬性的js,則需要等到100張圖片全部加載完成后才能執(zhí)行。而在這期間,頁(yè)面元素不會(huì)響應(yīng)你的任何操作,就好像“死”了一樣。如果是在實(shí)際項(xiàng)目中,用戶很可能不會(huì)等到你頁(yè)面所有東東加載完以后才去操作,在面對(duì)一個(gè)不會(huì)對(duì)自己的操作做任何響應(yīng)的頁(yè)面,唯一比較解氣的方式就是——果斷關(guān)掉~(yú)然后……就沒(méi)有了然后。
所以在實(shí)際應(yīng)用中,我們經(jīng)常會(huì)遇到這樣的場(chǎng)景,讓頁(yè)面加載后去做一些事情:綁定事件、DOM操作某些結(jié)點(diǎn)等。使用window.onload對(duì)于很多實(shí)際的應(yīng)用而言有點(diǎn)太“遲”了,比較影響用戶體驗(yàn)。那有沒(méi)有更好的方法解決這個(gè)問(wèn)題?比如提前到只要DOM樹(shù)創(chuàng)建完成之后就可以進(jìn)行如上操作呢?答案當(dāng)然是有的:DOMContentLoaded事件。
?
3、DOMContentLoaded
說(shuō)這個(gè)之前必須要提一下jQuery中的domReady機(jī)制。很多時(shí)候在使用jq也會(huì)出現(xiàn)最前面出現(xiàn)的那個(gè)TypeError,解決辦法就是把js放到j(luò)Query的ready回調(diào)里:
1 $(document).ready(function(){...});或者:
1 $(function(){...});這樣一來(lái),錯(cuò)誤妥妥地沒(méi)了。然后對(duì)比因果關(guān)系,大概得出一個(gè)結(jié)論:jQuery的ready回調(diào)應(yīng)該跟window.onload的效果原理是一樣的。恩,應(yīng)該是這樣。那我們就先來(lái)看一看jQuery(1.11.1)的ready回調(diào)是如何實(shí)現(xiàn)的:
1 jQuery.fn.ready = function( fn ) {2 // Add the callback3 jQuery.ready.promise().done( fn );4 return this;5 };6 jQuery.ready.promise = function( obj ) {7 if ( !readyList ) {8 readyList = jQuery.Deferred();9 if ( document.readyState === "complete" ) { 10 setTimeout( jQuery.ready ); 11 } else if ( document.addEventListener ) { 12 document.addEventListener( "DOMContentLoaded", completed, false ); 13 window.addEventListener( "load", completed, false ); 14 } else { 15 document.attachEvent( "onreadystatechange", completed ); 16 window.attachEvent( "onload", completed ); 17 var top = false; 18 try { 19 top = window.frameElement == null && document.documentElement; 20 } catch(e) {} 21 if ( top && top.doScroll ) { 22 (function doScrollCheck() { 23 if ( !jQuery.isReady ) { 24 try { 25 // Use the trick by Diego Perini 26 top.doScroll("left"); 27 } catch(e) { 28 return setTimeout( doScrollCheck, 50 ); 29 } 30 detach(); 31 jQuery.ready(); 32 } 33 })(); 34 } 35 } 36 } 37 return readyList.promise( obj ); 38 };看起來(lái)比想象中的window.onload要復(fù)雜呵。Jq的源碼中出現(xiàn)了DOMContentLoaded、readyState、onreadystatechange,這些跟domReady有什么關(guān)系?
我們還是先從DOMContentLoaded說(shuō)起吧。就如前面所述,很多時(shí)候我們會(huì)把js邏輯寫(xiě)在window.onload回調(diào)中,以防DOM樹(shù)還沒(méi)有建完就開(kāi)始對(duì)節(jié)點(diǎn)進(jìn)行操作從而導(dǎo)致錯(cuò)誤,而對(duì)于很多實(shí)際應(yīng)用來(lái)說(shuō),越早介入對(duì)DOM的干涉就越好,比如進(jìn)行特征偵測(cè)、事件綁定、DOM操作神馬的。domReady還可以滿足用戶提前綁定事件的需求,因?yàn)橛行┣闆r下頁(yè)面的圖片等外部資源過(guò)多,window.onload遲遲不能觸發(fā),這時(shí)若還沒(méi)有綁定事件,用戶點(diǎn)任何的按鈕都沒(méi)反應(yīng)(鏈接除外)會(huì)直接影響體驗(yàn)。
為了解決window.onload的短板,FF中便增加了一個(gè)DOMContentLoaded方法,與onload相比,DOMContentLoaded方法觸發(fā)的時(shí)間更早,它是在頁(yè)面的DOM樹(shù)創(chuàng)建完成后(也就是HTML解析第一步完成)即觸發(fā),而無(wú)需等待其他資源的加載。Webkit引擎從版本525(Webkit?nightly?1/2008:525+)開(kāi)始也引入了該事件,Opera中也包含該方法。到目前為止NB的IE仍然沒(méi)有要添加的意思。雖然IE下沒(méi)有,但解決辦法總是有的。于是對(duì)于那些忙前忙后的兼容小達(dá)人和死不悔改的頑固派,也就有了兩套策略:
1)支持DOMContentLoaded事件的,就使用DOMContentLoaded事件;
2)不支持的,就用來(lái)自Diego?Perini發(fā)現(xiàn)的著名Hack兼容。兼容原理大概就是,通過(guò)IE中的document.documentElement.doScroll(‘left’)來(lái)判斷DOM樹(shù)是否創(chuàng)建完畢。
Blabla了這么多,來(lái)看個(gè)IE模擬DOMContentLoaded例子吧。這個(gè)例子就來(lái)自上面發(fā)現(xiàn)IE下doScroll?Hackd的作者,細(xì)看也就是簡(jiǎn)化版的jQuery.ready回調(diào)的IE處理邏輯。
1 function IEContentLoaded (w, fn) {2 var d = w.document, done = false,3 // 只執(zhí)行一次用戶的回調(diào)函數(shù)init()4 init = function () {5 if (!done) {6 done = true;7 fn();8 }9 }; 10 (function () { 11 try { 12 // DOM樹(shù)未創(chuàng)建完之前調(diào)用doScroll會(huì)拋出錯(cuò)誤 13 d.documentElement.doScroll('left'); 14 } catch (e) { 15 //延遲再試一次~ 16 setTimeout(arguments.callee, 50); 17 return; 18 } 19 // 沒(méi)有錯(cuò)誤就表示DOM樹(shù)創(chuàng)建完畢,然后立馬執(zhí)行用戶回調(diào) 20 init(); 21 })(); 22 //監(jiān)聽(tīng)document的加載狀態(tài) 23 d.onreadystatechange = function() { 24 // 如果用戶是在domReady之后綁定的函數(shù),就立馬執(zhí)行 25 if (d.readyState == 'complete') { 26 d.onreadystatechange = null; 27 init(); 28 } 29 }; 30 }而對(duì)于高大上的chrome、ff等高級(jí)瀏覽器來(lái)說(shuō),對(duì)DOMContentLoaded事件的處理就相對(duì)來(lái)說(shuō)小case了,按照標(biāo)準(zhǔn)的事件綁定方式就可以處理:
1 if ( document.addEventListener ) { 2 document.addEventListener( "DOMContentLoaded", completed, false ); 3 }?
五、實(shí)例
看到這,想必大家已經(jīng)對(duì)DOMContentLoaded已經(jīng)有了新的認(rèn)識(shí),onload保險(xiǎn)絲也該適時(shí)換成智能電門(mén)啦~接下來(lái)就來(lái)個(gè)鮮活的例子,來(lái)讓大家更清晰的做下對(duì)比。
首先,頁(yè)面上有一組圖片:
1 <ul> 2 <li><img src="img/01.jpg" /></li> 3 <li><img src="img/02.jpg" /></li> 4 <li><img src="img/03.jpg" /></li> 5 <li><img src="img/04.jpg" /></li> 6 <li><img src="img/05.jpg" /></li> 7 </ul>頁(yè)面的js處理邏輯:
1 <script>2 var d = document;3 var msgBox = d.getElementById("showMsg");4 var imgs = d.getElementsByTagName("img");5 var time1 = null,time2 = null;6 if(d.addEventListener){7 d.addEventListener("DOMContentLoaded",domReady,false);8 }else{9 IEContentLoaded(domReady); 10 } 11 function domReady(){ 12 msgBox.innerHTML += "dom已加載!<br>"; 13 time1 = new Date().getTime(); 14 msgBox.innerHTML += "時(shí)間戳:" + time1 + "<br>"; 15 } 16 17 //兼容IE的domReady 18 function IEContentLoaded(fn){ 19 var done = false, 20 init = function(){ 21 if(!done){ 22 done = true; 23 fn(); 24 } 25 }; 26 (function(){ 27 try { 28 d.documentElement.doScroll('left'); 29 }catch(e){ 30 setTimeout(arguments.callee,50); 31 return; 32 } 33 init(); 34 })(); 35 d.onreadystatechange = function(){ 36 msgBox.innerHTML += "加載狀態(tài):" + d.readyState + "<br>"; 37 if(d.readyState == 'complete'){ 38 d.onreadystatechange = null; 39 } 40 } 41 } 42 window.onload = function(){ 43 msgBox.innerHTML += "onload已加載!<br>"; 44 time2 = new Date().getTime(); 45 msgBox.innerHTML += "時(shí)間戳:" + time2 + "<br>"; 46 msgBox.innerHTML +="domReady比onload快:" + (time2 - time1) + "ms<br>"; 47 } 48 </script>相信js腳本不用做過(guò)多解釋,在前面都已做過(guò)詳細(xì)分析,我們直接來(lái)看運(yùn)行結(jié)果:
很容易就能看出,DOMContentLoaded執(zhí)行5238ms之后才執(zhí)行的onload。這只是一個(gè)DEMO的差距,而如果是更大型的應(yīng)用,可能這個(gè)時(shí)間差距會(huì)更大。
?
轉(zhuǎn)載于:https://www.cnblogs.com/wang985850293/p/5231400.html
總結(jié)
以上是生活随笔為你收集整理的JS 实现 jQuery的$(function(){});的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: ongl 表达式
- 下一篇: javascript Date类型 学习