欧美vvv,亚洲第一成人在线,亚洲成人欧美日韩在线观看,日本猛少妇猛色XXXXX猛叫

新聞資訊

    看到這里,如果覺得累了,可以先休息下,這些概念需要被消化,畢竟后續將提到的事件循環機制就是基于事件觸發線程的,所以如果僅僅是看某個碎片化知識, 可能會有一種似懂非懂的感覺。要完成的梳理一遍才能快速沉淀,不易遺忘。放張圖鞏固下吧:

    再說一點,為什么JS引擎是單線程的?額,這個問題其實應該沒有標準答案,譬如,可能僅僅是因為由于多線程的復雜性,譬如多線程操作一般要加鎖,因此最初設計時選擇了單線程。。。

    進程和瀏覽器內核(進程)的通信過程

    看到這里,首先,應該對瀏覽器內的進程和線程都有一定理解了頁面渲染完成后執行js,那么接下來,再談談瀏覽器的進程(控制進程)是如何和內核通信的, 這點也理解后,就可以將這部分的知識串聯起來,從頭到尾有一個完整的概念。

    如果自己打開任務管理器,然后打開一個瀏覽器,就可以看到:任務管理器中出現了兩個進程(一個是主控進程,一個則是打開Tab頁的渲染進程), 然后在這前提下,看下整個的過程:(簡化了很多)

    進程接收到結果并將結果繪制出來

    這里繪一張簡單的圖:(很簡化)

    看完這一整套流程,應該對瀏覽器的運作有了一定理解了,這樣有了知識架構的基礎后,后續就方便往上填充內容。

    這塊再往深處講的話就涉及到瀏覽器內核源碼解析了,不屬于本文范圍。

    如果這一塊要深挖,建議去讀一些瀏覽器內核源碼解析文章,或者可以先看看參考下來源中的第一篇文章,寫的不錯

    梳理瀏覽器內核中線程之間的關系

    到了這里,已經對瀏覽器的運行有了一個整體的概念,接下來,先簡單梳理一些概念

    GUI渲染線程與JS引擎線程互斥

    由于是可操縱DOM的,如果在修改這些元素屬性同時渲染界面(即JS線程和UI線程同時運行),那么渲染線程前后獲得的元素數據就可能不一致了。

    因此為了防止渲染出現不可預期的結果,瀏覽器設置GUI渲染線程與JS引擎為互斥的關系,當JS引擎執行時GUI線程會被掛起, GUI更新則會被保存在一個隊列中等到JS引擎線程空閑時立即被執行。

    JS阻塞頁面加載

    從上述的互斥關系,可以推導出,JS如果執行時間過長就會阻塞頁面。

    譬如,假設JS引擎正在進行巨量的計算,此時就算GUI有更新,也會被保存到隊列中,等待JS引擎空閑后執行。然后,由于巨量計算,所以JS引擎很可能很久很久后才能空閑,自然會感覺到巨卡無比。

    所以,要盡量避免JS執行時間過長,這樣就會造成頁面的渲染不連貫,導致頁面渲染加載阻塞的感覺。

    ,JS的多線程?

    前文中有提到JS引擎是單線程的,而且JS執行時間過長會阻塞頁面,那么JS就真的對cpu密集型計算無能為力么?

    所以,后來HTML5中支持了Web 。

    MDN的官方解釋是:

    Web Worker為Web內容在后臺線程中運行腳本提供了一種簡單的方法。線程可以執行任務而不干擾用戶界面
    一個worker是使用一個構造函數創建的一個對象(e.g.?Worker())?運行一個命名的JavaScript文件?
    這個文件包含將在工作線程中運行的代碼;?workers?運行在另一個全局上下文中,不同于當前的window
    因此,使用?window快捷方式獲取當前全局的范圍?(而不是self)?在一個?Worker?內將返回錯誤
    

    這樣理解下:

    所以,如果有非常耗時的工作,請單獨開一個線程,這樣里面不管如何翻天覆地都不會影響JS引擎主線程, 只待計算出結果后,將結果通信給主線程即可,!

    而且注意下,JS引擎是單線程的,這一點的本質仍然未改變,可以理解是瀏覽器給JS引擎開的外掛,專門用來解決那些大量計算問題。

    其它,關于的詳解就不是本文的范疇了,因此不再贅述。

    既然都到了這里,就再提一下(避免后續將這兩個概念搞混)

    是瀏覽器所有頁面共享的,不能采用與同樣的方式實現,因為它不隸屬于某個進程,可以為多個進程共享使用

    看到這里,應該就很容易明白了,本質上就是進程和線程的區別。由獨立的進程管理,只是屬于進程下的一個線程

    簡單梳理下瀏覽器渲染流程

    本來是直接計劃開始談JS運行機制的,但想了想,既然上述都一直在談瀏覽器,直接跳到JS可能再突兀,因此,中間再補充下瀏覽器的渲染流程(簡單版本)

    為了簡化理解,前期工作直接省略成:(要展開的或完全可以寫另一篇超長文)

    -?瀏覽器輸入url,瀏覽器主進程接管,開一個下載線程,
    然后進行?http請求(略去DNS查詢,IP尋址等等操作),然后等待響應,獲取內容,
    隨后將內容通過RendererHost接口轉交給Renderer進程
    -?瀏覽器渲染流程開始
    

    瀏覽器器內核拿到內容后,渲染大概可以劃分成以下幾個步驟:

    解析html建立dom樹

    解析css構建樹(將CSS代碼解析成樹形的數據結構,然后結合DOM合并成樹)

    布局樹(/),負責各元素尺寸、位置的計算

    繪制樹(paint),繪制頁面像素信息

    瀏覽器會將各層的信息發送給GPU,GPU會將各層合成(),顯示在屏幕上。

    所有詳細步驟都已經略去,渲染完畢后就是load事件了,之后就是自己的JS邏輯處理了

    既然略去了一些詳細的步驟,那么就提一些可能需要注意的細節把。

    這里重繪參考來源中的一張圖:(參考來源第一篇)

    load事件與事件的先后

    上面提到,渲染完畢后會觸發load事件,那么你能分清楚load事件與事件的先后么?

    很簡單,知道它們的定義就可以了:

    (譬如如果有async加載的腳本就不一定完成)

    (渲染完畢了)

    所以,順序是: -> load

    css加載是否會阻塞dom樹渲染?

    這里說的是頭部引入css的情況

    首先,我們都知道:css是由單獨的下載線程異步下載的。

    然后再說下幾個現象:

    這可能也是瀏覽器的一種優化機制。

    因為你加載css的時候,可能會修改下面DOM節點的樣式, 如果css加載不阻塞樹渲染的話,那么當css加載完之后, 樹可能又得重新重繪或者回流了,這就造成了一些沒有必要的損耗。所以干脆就先把DOM樹的結構先解析完,把可以做的工作做完,然后等你css加載完之后, 在根據最終的樣式來渲染樹,這種做法性能方面確實會比較好一點。

    普通圖層和復合圖層

    渲染步驟中就提到了概念。

    可以簡單的這樣理解,瀏覽器渲染的圖層一般包含兩大類:普通圖層以及復合圖層

    首先,普通文檔流內可以理解為一個復合圖層(這里稱為默認復合層,里面不管添加多少元素,其實都是在同一個復合圖層中)

    其次,布局(fixed也一樣),雖然可以脫離普通文檔流,但它仍然屬于默認復合層。

    然后,可以通過硬件加速的方式,聲明一個新的復合圖層,它會單獨分配資源 (當然也會脫離普通文檔流,這樣一來,不管這個復合圖層中怎么變化,也不會影響默認復合層里的回流重繪)

    可以簡單理解下:GPU中,各個復合圖層是單獨繪制的,所以互不影響,這也是為什么某些場景硬件加速效果一級棒

    可以源碼調試 -> More Tools -> -> Layer 中看到,黃色的就是復合圖層信息

    如下圖。可以驗證上述的說法

    如何變成復合圖層(硬件加速)

    將該元素變成一個復合圖層,就是傳說中的硬件加速技術

    作用是提前告訴瀏覽器要變化,這樣瀏覽器會開始做一些優化工作(這個最好用完后就釋放)

    和硬件加速的區別

    可以看到,雖然可以脫離普通文檔流,但是無法脫離默認復合層。所以,就算中信息改變時不會改變普通文檔流中樹, 但是,瀏覽器最終繪制時,是整個復合層繪制的,所以中信息的改變,仍然會影響整個復合層的繪制。(瀏覽器會重繪它,如果復合層中內容多,帶來的繪制信息變化過大,資源消耗是非常嚴重的)

    而硬件加速直接就是在另一個復合層了(另起爐灶),所以它的信息改變不會影響默認復合層 (當然了,內部肯定會影響屬于自己的復合層),僅僅是引發最后的合成(輸出視圖)

    復合圖層的作用?

    一般一個元素開啟硬件加速后會變成復合圖層,可以獨立于普通文檔流中,改動后可以避免整個頁面重繪,提升性能

    但是盡量不要大量使用復合圖層,否則由于資源消耗過度,頁面反而會變的更卡

    硬件加速時請使用index

    使用硬件加速時,盡可能的使用index,防止瀏覽器默認給后續的元素創建復合層渲染

    具體的原理時這樣的: CSS3中,如果這個元素添加了硬件加速,并且index層級比較低, 那么在這個元素的后面其它元素(層級比這個元素高的,或者相同的頁面渲染完成后執行js,并且或屬性相同的), 會默認變為復合層渲染,如果處理不當會極大的影響性能

    簡單點理解,其實可以認為是一個隱式合成的概念:如果a是一個復合圖層,而且b在a上面,那么b也會被隱式轉為一個復合圖層,這點需要特別注意

    另外,這個問題可以在這個地址看到重現(原作者分析的挺到位的,直接上鏈接):

    從Event Loop談JS的運行機制

    到此時,已經是屬于瀏覽器頁面初次渲染完畢后的事情,JS引擎的一些運行機制分析。

    注意,這里不談可執行上下文,VO,scop chain等概念(這些完全可以整理成另一篇文章了),這里主要是結合Event Loop來談JS代碼是如何執行的。

    讀這部分的前提是已經知道了JS引擎是單線程,而且這里會用到上文中的幾個概念:(如果不是很理解,可以回頭溫習)

    然后再理解一個概念:

    看圖:

    看到這里,應該就可以理解了:為什么有時候推入的事件不能準時執行?因為可能在它推入到事件列表時,主線程還不空閑,正在執行其它代碼, 所以自然有誤差。

    事件循環機制進一步補充

    這里就直接引用一張圖片來協助理解:(參考自 的演講《Help, I'm stuck in an event-loop[1]》)

    上圖大致描述就是:

    棧中的代碼調用某些api時,它們會在事件隊列中添加各種事件(當滿足觸發條件后,如ajax請求完畢)

    單獨說說定時器

    上述事件循環機制的核心是:JS引擎線程和事件觸發線程

    但事件上,里面還有一些隱藏細節,譬如調用后,是如何等待特定時間后才添加到事件隊列中的?

    是JS引擎檢測的么?當然不是了。它是由定時器線程控制(因為JS引擎自己都忙不過來,根本無暇分身)

    為什么要單獨的定時器線程?因為引擎是單線程的, 如果處于阻塞線程狀態就會影響記計時的準確,因此很有必要單獨開一個線程用來計時。

    什么時候會用到定時器線程?當使用或時,它需要定時器線程計時,計時完成后就會將特定的事件推入事件隊列中。

    譬如:

    setTimeout(function(){
    ????console.log('hello!');
    },?1000);
    

    這段代碼的作用是當1000毫秒計時完畢后(由定時器線程計時),將回調函數推入事件隊列中,等待主線程執行

    setTimeout(function(){
    ????console.log('hello!');
    },?0);
    console.log('begin');
    

    這段代碼的效果是最快的時間內將回調函數推入事件隊列中,等待主線程執行

    注意:

    (不過也有一說是不同瀏覽器有不同的最小時間設定)

    而不是

    用模擬定期計時和直接用是有區別的。

    因為每次計時到后就會去執行,然后執行一段時間后才會繼續,中間就多了誤差 (誤差多少與代碼執行時間有關)

    而則是每次都精確的隔一段時間推入一個事件 (但是,事件的實際執行時間不一定就準確,還有可能是這個事件還沒執行完畢,下一個事件就來了)

    而且有一些比較致命的問題就是:

    就會導致定時器代碼連續運行好幾次,而之間沒有間隔。就算正常間隔執行,多個的代碼執行時間可能會比預期小(因為代碼執行需要一定時間)

    它會把的回調函數放在隊列中,等瀏覽器窗口再次打開時,一瞬間全部執行時

    所以,鑒于這么多但問題,目前一般認為的最佳方案是:用模擬,或者特殊場合直接用e

    補充:JS高程中有提到,JS引擎會對進行優化,如果當前事件隊列中有的回調,不會重復添加。不過,仍然是有很多問題。。。

    事件循環進階:與

    這段參考了參考來源中的第2篇文章(英文版的),(加了下自己的理解重新描述了下), 強烈推薦有英文基礎的同學直接觀看原文,作者描述的很清晰,示例也很不錯,如下:

    [2]

    上文中將JS事件循環機制梳理了一遍,在ES5的情況是夠用了,但是在ES6盛行的現在,仍然會遇到一些問題,譬如下面這題:

    console.log('script?start');
    setTimeout(function()?{
    ????console.log('setTimeout');
    },?0);
    Promise.resolve().then(function()?{
    ????console.log('promise1');
    }).then(function()?{
    ????console.log('promise2');
    });
    console.log('script?end');
    

    嗯哼,它的正確執行順序是這樣子的:

    script?start
    script?end
    promise1
    promise2
    setTimeout
    

    為什么呢?因為里有了一個一個新的概念:

    或者,進一步,JS中分為兩種任務類型:**和**,在中,稱為jobs,可稱為task

    它們的定義?區別?簡單點可以按如下理解:

    (`task->渲染->task->...`)
    

    分別很么樣的場景會形成和呢?

    __補充:在node環境下,.的優先級高于,也就是可以簡單理解為:在宏任務結束后會先執行微任務隊列中的部分,然后才會執行微任務中的部分。

    參考:

    再根據線程來理解下:

    (這點由自己理解+推測得出,因為它是在主線程下無縫執行的)

    所以,總結下運行機制:

    如圖:

    另外,請注意下的與官方版本的區別:

    注意,有一些瀏覽器執行結果不一樣(因為它們可能把當成來執行了), 但是為了簡單,這里不描述一些不標準的瀏覽器下的場景(但記住,有些瀏覽器可能并不標準)

    補充:使用實現

    可以用來實現 (它屬于,優先級小于, 一般是不支持時才會這樣做)

    它是HTML5中的新特性,作用是:監聽一個DOM變動, 當DOM對象樹發生任何變動時, 會得到通知

    像以前的Vue源碼中就是利用它來模擬的, 具體原理是,創建一個并監聽內容變化, 然后要的時候去改一下這個節點的文本內容, 如下:(Vue的源碼,未修改)

    var?counter?=?1
    var?observer?=?new?MutationObserver(nextTickHandler)
    var?textNode?=?document.createTextNode(String(counter))
    observer.observe(textNode,?{
    ????characterData:?true
    })
    timerFunc?=?()?=>?{
    ????counter?=?(counter?+?1)?%?2
    ????textNode.data?=?String(counter)
    }
    

    對應Vue源碼鏈接[3]

    不過,現在的Vue(2.5+)的實現移除了的方式(據說是兼容性原因), 取而代之的是使用 (當然,默認情況仍然是,不支持才兼容的)。

    屬于宏任務,優先級是:->, 所以Vue(2.5+)內部的與2.4及之前的實現是不一樣的,需要注意下。

網站首頁   |    關于我們   |    公司新聞   |    產品方案   |    用戶案例   |    售后服務   |    合作伙伴   |    人才招聘   |   

友情鏈接: 餐飲加盟

地址:北京市海淀區    電話:010-     郵箱:@126.com

備案號:冀ICP備2024067069號-3 北京科技有限公司版權所有