Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 欧美日韩国产精品,国产精品免费观看视频,亚洲国产综合久久精品

          整合營(yíng)銷服務(wù)商

          電腦端+手機(jī)端+微信端=數(shù)據(jù)同步管理

          免費(fèi)咨詢熱線:

          小鄭詳解JavaScript的運(yùn)行機(jī)制

          擊右上方紅色按鈕關(guān)注“小鄭搞碼事”,每天都能學(xué)到知識(shí),搞懂一個(gè)問(wèn)題!

          先來(lái)看這段代碼,大家看表示什么意思。

          一眼看過(guò)去,非常熟悉,通常我們會(huì)這樣理解:這段代碼表示1秒后,會(huì)執(zhí)行setTimeout里面那個(gè)函數(shù)。

          然而,這種解釋并不準(zhǔn)確, 那應(yīng)該怎么理解呢? 我們先來(lái)理解JavaScript的遠(yuǎn)行機(jī)制。

          本文最后我會(huì)給出JavaScript的運(yùn)行機(jī)制完整圖。但在這之前,我想先來(lái)給大家解釋幾個(gè)問(wèn)題。

          1. JavaScript為什么是單線程的?

          JavaScript設(shè)計(jì)的初衷是用在瀏覽器中, 那么,我們來(lái)想象一下,如果JavaScript是多線程的話。

          必然可以有兩個(gè)進(jìn)程,process1和process2,那么這兩個(gè)進(jìn)程可以同時(shí)對(duì)同一個(gè)DOM進(jìn)行操作。如果這個(gè)時(shí)候,一個(gè)進(jìn)程要?jiǎng)h除這個(gè)DOM,另一個(gè)進(jìn)程要編輯這個(gè)DOM。啟不是矛盾嘛。

          所以,這樣應(yīng)該更好理解,JS為什么是單線程了。

          2. JavaScript為什么需要異步?

          單線程為什么需要異步呢?

          JavaScript如果不存在異步,而是自上而下執(zhí)行,這樣的話,假如上一行解析時(shí)間很長(zhǎng),那么下面的代碼直接就會(huì)被阻塞。這種現(xiàn)象對(duì)于用戶來(lái)說(shuō),意味著"卡死"。嚴(yán)重影響用戶流失,這樣解釋好理解吧,所以JavaScript需要異步處理。

          3. 單線程如何實(shí)現(xiàn)異步呢?

          JavaScript竟然需要異步,那么它是如何實(shí)現(xiàn)異步的呢?

          JavaScript是通過(guò)事件循環(huán)(event loop)來(lái)實(shí)現(xiàn)的,事件循環(huán)機(jī)制也就是今天要說(shuō)的JavaScript運(yùn)行機(jī)制。

          (一)同步任務(wù)和異步任務(wù)

          來(lái)看一段代碼:

          首先這段代碼輸出結(jié)果是啥?

          輸出:1 3 2

          其中setTimeout需要延遲一段時(shí)間才去執(zhí)行,這類代碼就是異步代碼。

          看到這個(gè)結(jié)果,所以通常我們都這么理解JS的執(zhí)行原理:

          第一,判斷JS是同步還是異步,同步進(jìn)入主線程,異步則進(jìn)入event table。

          第二,異步任務(wù)在event table中注冊(cè)函數(shù),當(dāng)滿足觸發(fā)條件后,被推入event queue(事件隊(duì)列)。

          第三,同步任務(wù)進(jìn)入主線程后一直執(zhí)行,直到主線程空閑,才會(huì)去event queue中查看是否有可執(zhí)行的異步任務(wù),如果有就推入主線程。

          按到這個(gè)邏輯,上面這段實(shí)例代碼,是不是就很好理解了。1,3是同步任務(wù)進(jìn)入主要線程,自上而下執(zhí)行,2是異步任務(wù),滿足觸發(fā)條件后,推入事件隊(duì)列,等待主線程有空時(shí)調(diào)用。

          (二)宏任務(wù)(macro-task)和微任務(wù)(micro-task)

          然而,按照同步和異步任務(wù)來(lái)理解JS的運(yùn)行機(jī)制似乎并不準(zhǔn)確。

          來(lái)看一段代碼??纯此妮敵鲰樞?。

          上面這段代碼,按同步和異步的理解,輸出結(jié)果是:2,4,1,3。因?yàn)?,4是同步任務(wù),按順序在主線程自上而下執(zhí)行,而1,3是異步任務(wù),按順序在主線程有空后自先而后執(zhí)行。

          可事實(shí)輸出并不是這個(gè)結(jié)果,而是這樣的:2,4,3,1。為什么呢?來(lái)理解一下宏任務(wù)和微任務(wù)。

          寵任務(wù):包括整體script代碼,setTimeout,setInterval。

          微任務(wù):Promise,process.nextTick。

          來(lái)看原理圖:

          嗯,對(duì),這就是JS的運(yùn)行機(jī)制。也就是事件循環(huán)。解釋一下:

          第一,執(zhí)行一個(gè)宏任務(wù)(主線程的同步script代碼),過(guò)程中如果遇到微任務(wù),就將其放到微任務(wù)的事件隊(duì)列里。

          第二,當(dāng)前宏任務(wù)執(zhí)行完成后,會(huì)查微任務(wù)的事件隊(duì)列,將將全部的微任務(wù)依次執(zhí)行完,再去依次執(zhí)行宏任務(wù)事件隊(duì)列。

          上面代碼中promise的then是一微任務(wù),因此它的執(zhí)行在setTimeout之前。

          需要注意的是:在node環(huán)境下,process.nextTick的優(yōu)先級(jí)高于promise。也就是可以簡(jiǎn)單理解為,在宏任務(wù)結(jié)束后會(huì)先執(zhí)行微任務(wù)隊(duì)列中的nextTickQueue部分,然后才會(huì)執(zhí)行微任務(wù)中的promise部分。

          所以最后總結(jié)一下,對(duì)于文章一開(kāi)頭提到的那段代碼,我們可以準(zhǔn)確的理解為:

          1秒后,setTimeout里的函數(shù)會(huì)被推入event queue,而event queue(事件隊(duì)列)里的任務(wù),只有在主線程空閑時(shí)才會(huì)執(zhí)行。也就是需要同時(shí)滿足兩個(gè)條件(1)1秒后。(2)主線程必須空閑,這樣1秒后才會(huì)執(zhí)行該函數(shù)。

          現(xiàn)在,關(guān)于JavaScript的運(yùn)行機(jī)制,大家應(yīng)該都理解了,有問(wèn)題歡迎留言。

          事跟我說(shuō)他用jQuery取不到頁(yè)面上隱藏元素input的值,他的html頁(yè)面大概內(nèi)容如下。

          <!DOCTYPE html>
          <html lang="zh">
           
          <head>
          	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
          	<script type="text/javascript" src="jslib/jquery-1.11.2.min.js"></script>
          	<title>淺談Html頁(yè)面內(nèi)容執(zhí)行順序</title>
          	<script type="text/javascript">
          		var userId = $('#hiddenUserId').val();
          		var contextPath = $('#hiddenContextPath').val();
          		var userName = $('#hiddenUserName').val();
          	</script>
          </head>
           
          <body>
          	<input type="hidden" id="hiddenUserId" value="101" />
          	<input type="hidden" id="hiddenContextPath" value="/web" />
          	<input type="hidden" id="hiddenUserName" value="小明" />
          </body>
           
          </html>

          頁(yè)面中的JS腳本在head中,JS腳本要讀取的input在body中。瀏覽器對(duì)html頁(yè)面內(nèi)容的加載是順序加載,也就是在html頁(yè)面中前面先加載,因此當(dāng)加載到JS腳本時(shí),input還沒(méi)有加載到瀏覽器中。JS是一種解釋性的腳本,也是從上而下順序執(zhí)行,由于這段JS代碼是立即執(zhí)行的,所以當(dāng)JS在執(zhí)行的時(shí)候,讀取不到input的值。

          最直接的修改方法是把JS放到網(wǎng)頁(yè)的最下面執(zhí)行。

          <!DOCTYPE html>
          <html lang="zh">
           
          <head>
          	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
          	<script type="text/javascript" src="jslib/jquery-1.11.2.min.js"></script>
          	<title>淺談Html頁(yè)面內(nèi)容執(zhí)行順序</title>	
          </head>
           
          <body>
          	<input type="hidden" id="hiddenUserId" value="101" />
          	<input type="hidden" id="hiddenContextPath" value="/web" />
          	<input type="hidden" id="hiddenUserName" value="小明" />
          	
          	<script type="text/javascript">
          		var userId = $('#hiddenUserId').val();
          		var contextPath = $('#hiddenContextPath').val();
          		var userName = $('#hiddenUserName').val();
          	</script>
          </body>
           
          </html>

          把JS放到網(wǎng)頁(yè)的最下面,這樣在JS執(zhí)行的時(shí)候,網(wǎng)頁(yè)內(nèi)容都已經(jīng)加載完畢。把JS放在網(wǎng)頁(yè)的最下面方法并不是最好的解決方法,大部分情況JS并不是總能放在網(wǎng)頁(yè)的最下面。這時(shí)可以用window的onload事件,onload事件在整個(gè)頁(yè)面都加載完成后才觸發(fā),可以把JS腳本放在onload里面執(zhí)行。不同瀏覽器onload事件添加方式也不一樣。

          IE下事件:

          window.attachEvent('onload', function(){
          			var userId = $('#hiddenUserId').val();
          			var contextPath = $('#hiddenContextPath').val();
          			var userName = $('#hiddenUserName').val();
          		});

          Chrome/Firefox等DOM標(biāo)準(zhǔn)事件:

          window.addEventListener('load', function(){
          			var userId = $('#hiddenUserId').val();
          			var contextPath = $('#hiddenContextPath').val();
          			var userName = $('#hiddenUserName').val();
          		});

          由于不同瀏覽器的事件添加方式不一樣,jQuery為我們提供了通用的初始化方法,該方法在頁(yè)面加載完成時(shí)觸發(fā)。

          $(function(){
          			var userId = $('#hiddenUserId').val();
          			var contextPath = $('#hiddenContextPath').val();
          			var userName = $('#hiddenUserName').val();
          		});

          上面方法本質(zhì)就是添加onload監(jiān)聽(tīng)事件。

          最終修改后的頁(yè)面

          覽器的“心”

          瀏覽器的“心”,說(shuō)的就是瀏覽器的內(nèi)核。在研究瀏覽器微觀的運(yùn)行機(jī)制之前,我們首先要對(duì)瀏覽器內(nèi)核有一個(gè)宏觀的把握。

          許多工程師因?yàn)闃I(yè)務(wù)需要,免不了需要去處理不同瀏覽器下代碼渲染結(jié)果的差異性。這些差異性正是因?yàn)闉g覽器內(nèi)核的不同而導(dǎo)致的——瀏覽器內(nèi)核決定了瀏覽器解釋網(wǎng)頁(yè)語(yǔ)法的方式。

          瀏覽器內(nèi)核可以分成兩部分:渲染引擎(Layout Engine 或者 Rendering Engine)和 JS 引擎。早期渲染引擎和 JS 引擎并沒(méi)有十分明確的區(qū)分,但隨著 JS 引擎越來(lái)越獨(dú)立,內(nèi)核也成了渲染引擎的代稱(下文我們將沿用這種叫法)。渲染引擎又包括了 HTML 解釋器、CSS 解釋器、布局、網(wǎng)絡(luò)、存儲(chǔ)、圖形、音視頻、圖片解碼器等等零部件。

          目前市面上常見(jiàn)的瀏覽器內(nèi)核可以分為這四種:Trident(IE)、Gecko(火狐)、Blink(Chrome、Opera)、Webkit(Safari)。

          大家最耳熟能詳?shù)目赡芫褪?Webkit 內(nèi)核了。很多同學(xué)可能會(huì)聽(tīng)說(shuō)過(guò) Chrome 的內(nèi)核就是 Webkit,殊不知 Chrome 內(nèi)核早已迭代為了 Blink。但是換湯不換藥,Blink 其實(shí)也是基于 Webkit 衍生而來(lái)的一個(gè)分支,因此,Webkit 內(nèi)核仍然是當(dāng)下瀏覽器世界真正的霸主。

          下面我們就以 Webkit 為例,對(duì)現(xiàn)代瀏覽器的渲染過(guò)程進(jìn)行一個(gè)深度的剖析。

          開(kāi)啟瀏覽器渲染“黑盒”

          什么是渲染過(guò)程?簡(jiǎn)單來(lái)說(shuō),渲染引擎根據(jù) HTML 文件描述構(gòu)建相應(yīng)的數(shù)學(xué)模型,調(diào)用瀏覽器各個(gè)零部件,從而將網(wǎng)頁(yè)資源代碼轉(zhuǎn)換為圖像結(jié)果,這個(gè)過(guò)程就是渲染過(guò)程(如下圖)。

          從這個(gè)流程來(lái)看,瀏覽器呈現(xiàn)網(wǎng)頁(yè)這個(gè)過(guò)程,宛如一個(gè)黑盒。在這個(gè)神秘的黑盒中,有許多功能模塊,內(nèi)核內(nèi)部的實(shí)現(xiàn)正是這些功能模塊相互配合協(xié)同工作進(jìn)行的。其中我們最需要關(guān)注的,就是HTML 解釋器CSS 解釋器圖層布局計(jì)算模塊、視圖繪制模塊JavaScript 引擎這幾大模塊:

          1. HTML 解釋器:將 HTML 文檔經(jīng)過(guò)詞法分析輸出 DOM 樹(shù)。
          2. CSS 解釋器:解析 CSS 文檔, 生成樣式規(guī)則。
          3. 圖層布局計(jì)算模塊:布局計(jì)算每個(gè)對(duì)象的精確位置和大小。
          4. 視圖繪制模塊:進(jìn)行具體節(jié)點(diǎn)的圖像繪制,將像素渲染到屏幕上。
          5. JavaScript 引擎:編譯執(zhí)行 Javascript 代碼。

          瀏覽器渲染過(guò)程解析

          有了對(duì)零部件的了解打底,我們就可以一起來(lái)走一遍瀏覽器的渲染流程了。在瀏覽器里,每一個(gè)頁(yè)面的首次渲染都經(jīng)歷了如下階段(圖中箭頭不代表串行,有一些操作是并行進(jìn)行的,下文會(huì)說(shuō)明):

          • 解析 HTML

          在這一步瀏覽器執(zhí)行了所有的加載解析邏輯,在解析 HTML 的過(guò)程中發(fā)出了頁(yè)面渲染所需的各種外部資源請(qǐng)求。

          • 計(jì)算樣式

          瀏覽器將識(shí)別并加載所有的 CSS 樣式信息與 DOM 樹(shù)合并,最終生成頁(yè)面 render 樹(shù)(:after :before 這樣的偽元素會(huì)在這個(gè)環(huán)節(jié)被構(gòu)建到 DOM 樹(shù)中)。

          • 計(jì)算圖層布局

          頁(yè)面中所有元素的相對(duì)位置信息,大小等信息均在這一步得到計(jì)算。

          • 繪制圖層

          在這一步中瀏覽器會(huì)根據(jù)我們的 DOM 代碼結(jié)果,把每一個(gè)頁(yè)面圖層轉(zhuǎn)換為像素,并對(duì)所有的媒體文件進(jìn)行解碼。

          • 整合圖層,得到頁(yè)面

          最后一步瀏覽器會(huì)合并合各個(gè)圖層,將數(shù)據(jù)由 CPU 輸出給 GPU 最終繪制在屏幕上。(復(fù)雜的視圖層會(huì)給這個(gè)階段的 GPU 計(jì)算帶來(lái)一些壓力,在實(shí)際應(yīng)用中為了優(yōu)化動(dòng)畫(huà)性能,我們有時(shí)會(huì)手動(dòng)區(qū)分不同的圖層)。

          幾棵重要的“樹(shù)”

          上面的內(nèi)容沒(méi)有理解透徹?別著急,我們一起來(lái)捋一捋這個(gè)過(guò)程中的重點(diǎn)——樹(shù)!

          為了使渲染過(guò)程更明晰一些,我們需要給這些”樹(shù)“們一個(gè)特寫(xiě):

          1. DOM 樹(shù):解析 HTML 以創(chuàng)建的是 DOM 樹(shù)(DOM tree ):渲染引擎開(kāi)始解析 HTML 文檔,轉(zhuǎn)換樹(shù)中的標(biāo)簽到 DOM 節(jié)點(diǎn),它被稱為“內(nèi)容樹(shù)”。
          2. CSSOM 樹(shù):解析 CSS(包括外部 CSS 文件和樣式元素)創(chuàng)建的是 CSSOM 樹(shù)。CSSOM 的解析過(guò)程與 DOM 的解析過(guò)程是并行的
          3. 渲染樹(shù):CSSOM 與 DOM 結(jié)合,之后我們得到的就是渲染樹(shù)(Render tree )。
          4. 布局渲染樹(shù):從根節(jié)點(diǎn)遞歸調(diào)用,計(jì)算每一個(gè)元素的大小、位置等,給每個(gè)節(jié)點(diǎn)所應(yīng)該出現(xiàn)在屏幕上的精確坐標(biāo),我們便得到了基于渲染樹(shù)的布局渲染樹(shù)(Layout of the render tree)。
          5. 繪制渲染樹(shù): 遍歷渲染樹(shù),每個(gè)節(jié)點(diǎn)將使用 UI 后端層來(lái)繪制。整個(gè)過(guò)程叫做繪制渲染樹(shù)(Painting the render tree)。

          基于這些“樹(shù)”,我們?cè)偈崂硪环?/p>

          渲染過(guò)程說(shuō)白了,首先是基于 HTML 構(gòu)建一個(gè) DOM 樹(shù),這棵 DOM 樹(shù)與 CSS 解釋器解析出的 CSSOM 相結(jié)合,就有了布局渲染樹(shù)。最后瀏覽器以布局渲染樹(shù)為藍(lán)本,去計(jì)算布局并繪制圖像,我們頁(yè)面的初次渲染就大功告成了。

          之后每當(dāng)一個(gè)新元素加入到這個(gè) DOM 樹(shù)當(dāng)中,瀏覽器便會(huì)通過(guò) CSS 引擎查遍 CSS 樣式表,找到符合該元素的樣式規(guī)則應(yīng)用到這個(gè)元素上,然后再重新去繪制它。

          有心的同學(xué)可能已經(jīng)在思考了,查表是個(gè)花時(shí)間的活,我怎么讓瀏覽器的查詢工作又快又好地實(shí)現(xiàn)呢?OK,講了這么多原理,我們終于引出了我們的第一個(gè)可轉(zhuǎn)化為代碼的優(yōu)化點(diǎn)——CSS 樣式表規(guī)則的優(yōu)化!

          不做無(wú)用功:基于渲染流程的 CSS 優(yōu)化建議

          在給出 CSS 選擇器方面的優(yōu)化建議之前,先告訴大家一個(gè)小知識(shí):CSS 引擎查找樣式表,對(duì)每條規(guī)則都按從右到左的順序去匹配。 看如下規(guī)則:

          #myList li {}
          

          這樣的寫(xiě)法其實(shí)很常見(jiàn)。大家平時(shí)習(xí)慣了從左到右閱讀的文字閱讀方式,會(huì)本能地以為瀏覽器也是從左到右匹配 CSS 選擇器的,因此會(huì)推測(cè)這個(gè)選擇器并不會(huì)費(fèi)多少力氣:#myList 是一個(gè) id 選擇器,它對(duì)應(yīng)的元素只有一個(gè),查找起來(lái)應(yīng)該很快。定位到了 myList 元素,等于是縮小了范圍后再去查找它后代中的 li 元素,沒(méi)毛病。

          事實(shí)上,CSS 選擇符是從右到左進(jìn)行匹配的。我們這個(gè)看似“沒(méi)毛病”的選擇器,實(shí)際開(kāi)銷相當(dāng)高:瀏覽器必須遍歷頁(yè)面上每個(gè) li 元素,并且每次都要去確認(rèn)這個(gè) li 元素的父元素 id 是不是 myList,你說(shuō)坑不坑!

          說(shuō)到坑,不知道大家還記不記得這個(gè)經(jīng)典的通配符:

          * {}
          

          入門(mén) CSS 的時(shí)候,不少同學(xué)拿通配符清除默認(rèn)樣式(我曾經(jīng)也是通配符用戶的一員)。但這個(gè)家伙很恐怖,它會(huì)匹配所有元素,所以瀏覽器必須去遍歷每一個(gè)元素!大家低頭看看自己頁(yè)面里的元素個(gè)數(shù),是不是心涼了——這得計(jì)算多少次呀!

          這樣一看,一個(gè)小小的 CSS 選擇器,也有不少的門(mén)道!好的 CSS 選擇器書(shū)寫(xiě)習(xí)慣,可以為我們帶來(lái)非??捎^的性能提升。根據(jù)上面的分析,我們至少可以總結(jié)出如下性能提升的方案:

          1. 避免使用通配符,只對(duì)需要用到的元素進(jìn)行選擇。
          2. 關(guān)注可以通過(guò)繼承實(shí)現(xiàn)的屬性,避免重復(fù)匹配重復(fù)定義。
          3. 少用標(biāo)簽選擇器。如果可以,用類選擇器替代,舉個(gè)例子:

          錯(cuò)誤示范:

          #myList li{}
          

          理想:

          .myList_li {}
          

          不要畫(huà)蛇添足,id 和 class 選擇器不應(yīng)該被多余的標(biāo)簽選擇器拖后腿。

          錯(cuò)誤示范

          .myList#title
          

          理想:

          #title
          

          減少嵌套。后代選擇器的開(kāi)銷是最高的,因此我們應(yīng)該盡量將選擇器的深度降到最低(最高不要超過(guò)三層),盡可能使用類來(lái)關(guān)聯(lián)每一個(gè)標(biāo)簽元素。

          搞定了 CSS 選擇器,萬(wàn)里長(zhǎng)征才剛剛開(kāi)始的第一步。但現(xiàn)在你已經(jīng)理解了瀏覽器的工作過(guò)程,接下來(lái)的征程對(duì)你來(lái)說(shuō)并不再是什么難題~

          告別阻塞:CSS 與 JS 的加載順序優(yōu)化

          說(shuō)完了過(guò)程,我們來(lái)說(shuō)一說(shuō)特性。

          HTML、CSS 和 JS,都具有阻塞渲染的特性。

          HTML 阻塞,天經(jīng)地義——沒(méi)有 HTML,何來(lái) DOM?沒(méi)有 DOM,渲染和優(yōu)化,都是空談。

          那么 CSS 和 JS 的阻塞又是怎么回事呢?

          CSS 的阻塞

          在剛剛的過(guò)程中,我們提到 DOM 和 CSSOM 合力才能構(gòu)建渲染樹(shù)。這一點(diǎn)會(huì)給性能造成嚴(yán)重影響:默認(rèn)情況下,CSS 是阻塞的資源。瀏覽器在構(gòu)建 CSSOM 的過(guò)程中,不會(huì)渲染任何已處理的內(nèi)容。即便 DOM 已經(jīng)解析完畢了,只要 CSSOM 不 OK,那么渲染這個(gè)事情就不 OK(這主要是為了避免沒(méi)有 CSS 的 HTML 頁(yè)面丑陋地“裸奔”在用戶眼前)。

          我們知道,只有當(dāng)我們開(kāi)始解析 HTML 后、解析到 link 標(biāo)簽或者 style 標(biāo)簽時(shí),CSS 才登場(chǎng),CSSOM 的構(gòu)建才開(kāi)始。很多時(shí)候,DOM 不得不等待 CSSOM。因此我們可以這樣總結(jié):

          CSS 是阻塞渲染的資源。需要將它盡早、盡快地下載到客戶端,以便縮短首次渲染的時(shí)間。

          事實(shí)上,現(xiàn)在很多團(tuán)隊(duì)都已經(jīng)做到了盡早(將 CSS 放在 head 標(biāo)簽里)和盡快(啟用 CDN 實(shí)現(xiàn)靜態(tài)資源加載速度的優(yōu)化)。這個(gè)“把 CSS 往前放”的動(dòng)作,對(duì)很多同學(xué)來(lái)說(shuō)已經(jīng)內(nèi)化為一種編碼習(xí)慣。那么現(xiàn)在我們還應(yīng)該知道,這個(gè)“習(xí)慣”不是空穴來(lái)風(fēng),它是由 CSS 的特性決定的。

          JS 的阻塞

          不知道大家注意到?jīng)]有,前面我們說(shuō)過(guò)程的時(shí)候,花了很多筆墨去說(shuō) HTML、說(shuō) CSS。相比之下,JS 的出鏡率也太低了點(diǎn)。

          這當(dāng)然不是因?yàn)?JS 不重要。而是因?yàn)椋谑状武秩具^(guò)程中,JS 并不是一個(gè)非登場(chǎng)不可的角色——沒(méi)有 JS,CSSOM 和 DOM 照樣可以組成渲染樹(shù),頁(yè)面依然會(huì)呈現(xiàn)——即使它死氣沉沉、毫無(wú)交互。

          JS 的作用在于修改,它幫助我們修改網(wǎng)頁(yè)的方方面面:內(nèi)容、樣式以及它如何響應(yīng)用戶交互。這“方方面面”的修改,本質(zhì)上都是對(duì) DOM 和 CSSDOM 進(jìn)行修改。因此 JS 的執(zhí)行會(huì)阻止 CSSOM,在我們不作顯式聲明的情況下,它也會(huì)阻塞 DOM。

          我們通過(guò)一個(gè)例子來(lái)理解一下這個(gè)機(jī)制:

          三個(gè) console 的結(jié)果分別為:

          注:本例僅使用了內(nèi)聯(lián) JS 做測(cè)試。感興趣的同學(xué)可以把這部分 JS 當(dāng)做外部文件引入看看效果——它們的表現(xiàn)一致。

          第一次嘗試獲取 id 為 container 的 DOM 失敗,這說(shuō)明 JS 執(zhí)行時(shí)阻塞了 DOM,后續(xù)的 DOM 無(wú)法構(gòu)建;第二次才成功,這說(shuō)明腳本塊只能找到在它前面構(gòu)建好的元素。這兩者結(jié)合起來(lái),“阻塞 DOM”得到了驗(yàn)證。再看第三個(gè) console,嘗試獲取 CSS 樣式,獲取到的是在 JS 代碼執(zhí)行前的背景色(yellow),而非后續(xù)設(shè)定的新樣式(blue),說(shuō)明 CSSOM 也被阻塞了。那么在阻塞的背后,到底發(fā)生了什么呢?

          我們前面說(shuō)過(guò),JS 引擎是獨(dú)立于渲染引擎存在的。我們的 JS 代碼在文檔的何處插入,就在何處執(zhí)行。當(dāng) HTML 解析器遇到一個(gè) script 標(biāo)簽時(shí),它會(huì)暫停渲染過(guò)程,將控制權(quán)交給 JS 引擎。JS 引擎對(duì)內(nèi)聯(lián)的 JS 代碼會(huì)直接執(zhí)行,對(duì)外部 JS 文件還要先獲取到腳本、再進(jìn)行執(zhí)行。等 JS 引擎運(yùn)行完畢,瀏覽器又會(huì)把控制權(quán)還給渲染引擎,繼續(xù) CSSOM 和 DOM 的構(gòu)建。 因此與其說(shuō)是 JS 把 CSS 和 HTML 阻塞了,不如說(shuō)是 JS 引擎搶走了渲染引擎的控制權(quán)。

          現(xiàn)在理解了阻塞的表現(xiàn)與原理,我們開(kāi)始思考一個(gè)問(wèn)題。瀏覽器之所以讓 JS 阻塞其它的活動(dòng),是因?yàn)樗恢?JS 會(huì)做什么改變,擔(dān)心如果不阻止后續(xù)的操作,會(huì)造成混亂。但是我們是寫(xiě) JS 的人,我們知道 JS 會(huì)做什么改變。假如我們可以確認(rèn)一個(gè) JS 文件的執(zhí)行時(shí)機(jī)并不一定非要是此時(shí)此刻,我們就可以通過(guò)對(duì)它使用 defer 和 async 來(lái)避免不必要的阻塞,這里我們就引出了外部 JS 的三種加載方式。

          JS的三種加載方式

          • 正常模式:
          <script src="index.js"></script>
          

          這種情況下 JS 會(huì)阻塞瀏覽器,瀏覽器必須等待 index.js 加載和執(zhí)行完畢才能去做其它事情。

          • async 模式:
          <script async src="index.js"></script>
          

          async 模式下,JS 不會(huì)阻塞瀏覽器做任何其它的事情。它的加載是異步的,當(dāng)它加載結(jié)束,JS 腳本會(huì)立即執(zhí)行。

          • defer 模式:
          <script defer src="index.js"></script>
          

          defer 模式下,JS 的加載是異步的,執(zhí)行是被推遲的。等整個(gè)文檔解析完成、DOMContentLoaded 事件即將被觸發(fā)時(shí),被標(biāo)記了 defer 的 JS 文件才會(huì)開(kāi)始依次執(zhí)行。

          從應(yīng)用的角度來(lái)說(shuō),一般當(dāng)我們的腳本與 DOM 元素和其它腳本之間的依賴關(guān)系不強(qiáng)時(shí),我們會(huì)選用 async;當(dāng)腳本依賴于 DOM 元素和其它腳本的執(zhí)行結(jié)果時(shí),我們會(huì)選用 defer。

          通過(guò)審時(shí)度勢(shì)地向 script 標(biāo)簽添加 async/defer,我們就可以告訴瀏覽器在等待腳本可用期間不阻止其它的工作,這樣可以顯著提升性能。


          主站蜘蛛池模板: 亚洲日韩AV一区二区三区中文 | 无码国产伦一区二区三区视频| 久久人妻av一区二区软件| 精产国品一区二区三产区| 麻豆一区二区免费播放网站| 亚洲乱色熟女一区二区三区蜜臀| 亚洲视频一区在线| 国产一区二区三区樱花动漫| 波多野结衣AV无码久久一区| 亚洲av日韩综合一区二区三区| 精品国产高清自在线一区二区三区| 国产福利电影一区二区三区,免费久久久久久久精 | 久久精品国产一区二区电影| 亚洲福利电影一区二区?| 一区二区视频在线免费观看| 99久久人妻精品免费一区| 国产乱码精品一区二区三区四川 | 亚洲av午夜精品一区二区三区| 国产亚洲一区二区三区在线| 日韩成人一区ftp在线播放| 亚洲AV成人一区二区三区AV| 内射白浆一区二区在线观看| 国产情侣一区二区| 亚洲AV无码片一区二区三区| 亚洲日本一区二区| 一区二区三区在线|欧| 日产一区日产2区| 成人区精品人妻一区二区不卡| 一本色道久久综合一区| 国产福利电影一区二区三区久久久久成人精品综合 | 久久精品日韩一区国产二区| 好看的电影网站亚洲一区| 国产福利91精品一区二区三区| 亚洲AV无码一区二区三区DV| 日韩免费一区二区三区在线播放| 在线精品自拍亚洲第一区| 一区二区不卡久久精品| 91video国产一区| 97精品一区二区视频在线观看| 久久一区二区三区精华液使用方法| 国产亚洲一区二区精品|