日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > HTML >内容正文

HTML

深入理解浏览器解析和执行过程

發(fā)布時間:2025/6/17 HTML 64 豆豆
生活随笔 收集整理的這篇文章主要介紹了 深入理解浏览器解析和执行过程 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

在我們公司的業(yè)務場景中,有很大一部分用戶是使用老款安卓機瀏覽頁面,這些老款安卓機性能較差,如果優(yōu)化不足,頁面的卡頓現象會更加明顯,此時頁面性能優(yōu)化的重要性就凸顯出現。優(yōu)化頁面的性能,需要對瀏覽器的渲染過程有深入的了解,針對瀏覽器的每一步環(huán)節(jié)進行優(yōu)化。

頁面高性能的判斷標準是 60fps。這是因為目前大多數設備的屏幕刷新率為 60 次/秒,也就是 60fps , 如果刷新率降低,也就是說出現了掉幀, 對于用戶來說,就是出現了卡頓的現象。

這就要求,頁面每一幀的渲染時間僅為16毫秒 (1 秒/ 60 = 16.66 毫秒)。但實際上,瀏覽器有其他工作要做,因此這一幀所有工作需要在 10毫秒內完成。如果工作沒有完成,幀率將下降,并且內容會在屏幕上抖動。 此現象通常稱為卡頓,會對用戶體驗產生負面影響。

瀏覽器渲染流程

瀏覽器一開始會從網絡層獲取請求文檔的內容,請求過來的數據是 Bytes,然后瀏覽器將其編譯成HTML的代碼。

但是我們寫出來的HTML代碼瀏覽器是看不懂的,所以需要進行解析。

渲染引擎解析 HTML 文檔,將各個dom標簽逐個轉化成“DOM tree”上的 DOM 節(jié)點。同時也會解析內部和外部的css, 解析為CSSOM tree, css tree和dom tree結合在一起生成了render tree。

render tree構建好之后,渲染引擎隨后會經歷一個layout的階段: 計算出每一個節(jié)點應該出現在屏幕上的確切坐標。

之后的階段被稱為paiting階段,渲染引擎會遍歷render tree, 然后由用戶界面后端層將每一個節(jié)點繪制出來。

最后一個階段是 composite 階段,這個階段是合并圖層。

瀏覽器內核

瀏覽器是一個極其復雜龐大的軟件。常見的瀏覽器有chrome, firefox。firefox是完全開源,Chrome不開源,但Chromium項目是部分開源。

Chromium和Chrome之間的關系類似于嘗鮮版和正式版的關系,Chromium會有很多新的不穩(wěn)定的特性,待成熟穩(wěn)定后會應用到Chrome。

瀏覽器功能有很多,包括網絡、資源管理、網頁瀏覽、多頁面管理、插件和擴展、書簽管理、歷史記錄管理、設置管理、下載管理、賬戶和同步、安全機制、隱私管理、外觀主題、開發(fā)者工具等。

因此瀏覽器內部被劃分為不同的模塊。其中和頁面渲染相關的,是下圖中虛線框的部分渲染引擎。

渲染引擎的作用是將頁面轉變成可視化的圖像結果。

目前,主流的渲染引擎包括Trident、GeckoWebKit,它們分別是IE、火狐和Chrome的內核(2013年,Google宣布了Blink內核,它其實是從WebKit復制出去的),其中占有率最高的是 WebKit。

WebKit

最早,蘋果公司和KDE開源社區(qū)產生了分歧,復制出一個開源的項目,就是WebKit。

WebKit被很多瀏覽器采用作為內核,其中就包括goole的chrome。

后來google公司又和蘋果公司產生了分歧,google從webkit中復制出一個blink項目。

因此,blink內核和webkit內核沒有特別的不同,因此很多老外會借用 chromium的實現來理解webkit的技術內幕,也是完全可以的。

瀏覽器源碼

瀏覽器的代碼非常的龐大,曾經有人嘗試閱讀Chromium項目的源碼,git clone 到本地發(fā)現有10個G,光編譯時間就3個小時(據說火狐瀏覽器編譯需要更多的時間,大約為6個小時)。因此關于瀏覽器內部究竟是如何運作的,大部分的分享是瀏覽器廠商參與研發(fā)的內部員工。

國外有個非常有毅力的工程師Tali Garsiel 花費了n年的時間探究了瀏覽器的內幕,本文關于瀏覽器內部工作原理的介紹,主要整理自她的博客how browser work , 和其他人的一些分享。

國內關于瀏覽器技術內幕主要有《WebKit技術內幕》

下面,我們將針對瀏覽器渲染的環(huán)節(jié),深入理解瀏覽器內核做了哪些事情,逐一的介紹如何去進行前端頁面的優(yōu)化。

瀏覽器渲染第一步:解析

解析是瀏覽器渲染引擎中第一個環(huán)節(jié)。我們先大致了解一下解析到底是怎么一回事。

什么是解析

通俗來講,解析文檔是指將文檔轉化成為有意義的結構,好讓代碼去使用他們

以上圖為例,右邊就是解析好的樹狀結構,這個結構就可以“喂“給其他的程序, 然后其他的程序就可以利用這個結構,生成一些計算的結果。

解析的過程可以分成兩個子過程:lexical analysis(詞法分析)syntax analysis(句法分析)。

lexical analysis(詞法分析)

lexical analysis 被稱為詞法分析的過程,有的文章也稱為 tokenization,其實就是把輸入的內容分為不同的tokens(標記),tokens是最小的組成部分,tokens就像是人類語言中的一堆詞匯。比如說,我們對一句英文進行l(wèi)exical analysis——“The quick brown fox jumps”,我們可以拿到以下的token:

  • “The”
  • “quick”
  • “brown”
  • “fox”
  • “jumps”

用來做lexical analysis的工具,被稱為**lexer**, 它負責把輸入的內容拆分為不同的tokens。不同的瀏覽器內核會選擇不同的lexer , 比如說webkit 是使用Flex (Fast Lexer)作為lexer。

syntax analysis(句法分析)

syntax analysis是應用語言句法中的規(guī)則, 簡單來說,就是判斷一串tokens組成的句子是不是正確的。

如果我說:“我吃飯工作完了”, 這句話是不符合syntax analysis的,雖然里面的每一個token都是正確的,但是不符合語法規(guī)范。需要注意的是,符合語法正確 的句子不一定是符合語義正確的。比如說,“一個綠色的夢想沉沉的睡去了”,從語法的角度來講,形容詞 + 主語 + 副詞 + 動詞沒有問題,但是語義上卻是什么鬼。

負責syntax analysis工作的是**parser**,解析是一個不斷往返的過程。

如下圖所示,parser向lexer要一個新的token,lexer會返回一個token, parser拿到token之后,會嘗試將這個token與某條語法規(guī)則進行匹配。

如果該token匹配上了語法規(guī)則,parser會將一個對應的節(jié)點添加到 parse tree (解析樹,如果是html就是dom tree,如果是css就是 cssom tree)中,然后繼續(xù)問parser要下一個node。

當然,也有可能該tokens沒有匹配上語法規(guī)則,parser會將tokens暫時保存,然后繼續(xù)問lexer要tokens, 直至找到可與所有內部存儲的標記匹配的規(guī)則。如果找不到任何匹配規(guī)則,parser就會引發(fā)一個異常。這意味著文檔無效,包含語法錯誤。

syntax analysis 的輸出結果是parse tree, parse tree 的結構表示了句法結構。比如說我們輸入"John hit the ball"作為一句話,那么 syntax analysis 的結果就是:

一旦我們拿到了parse tree, 還有最后一步工作沒有做,那就是:translation,還有一些博客將這個過程成為 compilation / transpilation / interpretation

Lexicons 和 Syntaxes

上面提到了lexer 和 parser 這兩個用于解析工具,我們通常不會自己寫,而是用現有的工具去生成。我們需要提供一個語言的 lexicon 和 syntaxes ,才可以生成相應的 lexer 和 parser 。

webkit 使用的 lexer 和 parser 是 FlexBison 。

  • flex 和css 的 flex 布局沒有關系,是 fast-lexer 的簡寫,用來生成 lexer。 它需要一個lexicon,這個lexicon 是用一堆正則表達式來定義的 。
  • bison 用來生成parsers, 它需要一個符合BNF范式的syntax。
  • lexicons

    lexicons 是通過正則表達式被定義的,比如說,js中的保留字,就是lexicons 的一部分。

    下面就是js中的保留字的正則表達式 的一部分。

    /^(?!(?:do|if|in|for|let|new|try|var|case|else|enum|eval|false|null|this|true|void|with|break|catch|class|const|super|throw|while|yield|delete|export|import|public|return|static|switch|typeof|default|extends|finally|package|private|continue|debugger|function|arguments|interface|protected|implements|instanceof)$)*$/ 復制代碼

    syntaxes

    syntaxes 通常是被一個叫無上下文語法所定義,關于無上下文語法可以點擊這個鏈接,反正只需要知道,無上下文語法要比常規(guī)的語法更復雜就好了。

    BNF范式

    非科班出身的前端可能不了解 BNF 范式(說的就是我 --),它是一種形式化符號來描述給定語言的語法。

    它的內容大致為:

  • 在雙引號中的字("word")代表著這些字符本身。
  • 而double_quote用來代表雙引號。
  • 在雙引號外的字(有可能有下劃線)代表著語法部分。
  • 尖括號( < > )內包含的為必選項。
  • 方括號( [ ] )內包含的為可選項。
  • 大括號( { } )內包含的為可重復0至無數次的項。
  • 豎線( | )表示在其左右兩邊任選一項,相當于"OR"的意思。
  • ::= 是“被定義為”的意思。
  • 下面是用BNF來定義的Java語言中的For語句的實例。

    FOR_STATEMENT ::= "for" "(" ( variable_declaration | ( expression ";" ) | ";" ) [ expression ] ";" [ expression ] ")" statement 復制代碼

    BNF 的誕生還是挺有意思的一件事情, 有了BNF才有了真正意義上的計算機語言。巴科斯范式直到今天,仍然是個迷,巴科斯是如何想到的

    小結

    我們現在對解析過程有了一個大致的了解,總結成一張圖就是這樣:

    對解析(parse)有了初步的了解之后,我們看一下HTML的解析過程。

    解析HTML

    HTML是不規(guī)范的,我們在寫html的代碼時候,比如說漏了一個閉合標簽,瀏覽器也可以正常渲染沒有問題的。這是一把雙刃劍,我們可以很容易的編寫html, 但是卻給html的解析帶來不少的麻煩,更詳細的信息可以點擊:鏈接

    HTML lexicon

    Html 的 lexicon 主要包括6個部分:

    • doctype
    • start tag
    • end tag
    • comment
    • character
    • End-of-file

    當一個html文檔被lexer 處理的時候,lexer 從文檔中一個字符一個字符的讀出來,并且使用 finite-state machine 來判斷一個完整的token是否已經被完整的收到了。

    HTML syntax

    這里就是html 解析的復雜所在了。html 標簽的容錯性很高,需要上下文敏感的語法。

    比如說對于下面兩段代碼:

    <!DOCTYPE html> <html lang="en-US"><head><title>Valid HTML</title></head><body><p>This is a paragraph. <span>This is a span.</span></p><div>This is a div.</div></body> </html> 復制代碼<html lAnG = EN-US> <p>This is a paragraph. <span>This is a span. <div>This is a div. 復制代碼

    第一段是規(guī)范的html代碼,第二段代碼有非常多的錯誤,但是這兩段代碼在瀏覽器中都是大致相同的結構:

    上面兩處代碼渲染出來的唯一的不同就是,正確的html會在頭部有<!DOCTYPE html>, 這行代碼會觸發(fā)瀏覽器的標準模式。

    所以你看,html 的容錯性是非常高的,這樣是有代價的,這增加了解析的困難,讓詞法解析解析更加困難。

    DOM Tree

    HTML 解析出來的產物,經過加工,就得到了DOM Tree。

    對于下面這種html的結構:

    <!DOCTYPE html> <html lang="en-US"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=Edge"><meta name="viewport"content="width=device-width, initial-scale=1, maximum-scale=1"></head><body><p>This is text in a paragraph.<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/1/14/Rubber_Duck_%288374802487%29.jpg/220px-Rubber_Duck_%288374802487%29.jpg"></p><div>This is text in a div.</div></body> </html> 復制代碼

    上面的html 的結構解析出來應該是:

    說完了html的解析,我們就該說CSS的解析了。

    解析CSS

    和html 解析相比,css 的解析就簡單很多了。

    CSS lexicon

    關于css的 lexicon,?the W3C’s CSS2 Level 2 specification 中已經給出了。

    CSS 中的 token 被列在了下面,下面的定義是采用了Lex風格的正則表達式。

    IDENT {ident} ATKEYWORD @{ident} STRING {string} BAD_STRING {badstring} BAD_URI {baduri} BAD_COMMENT {badcomment} HASH #{name} NUMBER {num} PERCENTAGE {num}% DIMENSION {num}{ident} URI url\({w}{string}{w}\) |url\({w}([!#$%&*-\[\]-~]|{nonascii}|{escape})*{w}\) UNICODE-RANGE u\+[0-9a-f?]{1,6}(-[0-9a-f]{1,6})? CDO <!-- CDC --> : : ; ; { \{ } \} ( \( ) \) [ \[ ] \] S [ \t\r\n\f]+ COMMENT \/\*[^*]*\*+([^/*][^*]*\*+)*\/ FUNCTION {ident}\( INCLUDES ~= DASHMATCH |= DELIM any other character not matched by the above rules, and neither a single nor a double quote 復制代碼

    花括號里面的宏被定義成如下:

    ident [-]?{nmstart}{nmchar}* name {nmchar}+ nmstart [_a-z]|{nonascii}|{escape} nonascii [^\0-\237] unicode \\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])? escape {unicode}|\\[^\n\r\f0-9a-f] nmchar [_a-z0-9-]|{nonascii}|{escape} num [0-9]+|[0-9]*\.[0-9]+ string {string1}|{string2} string1 \"([^\n\r\f\\"]|\\{nl}|{escape})*\" string2 \'([^\n\r\f\\']|\\{nl}|{escape})*\' badstring {badstring1}|{badstring2} badstring1 \"([^\n\r\f\\"]|\\{nl}|{escape})*\\? badstring2 \'([^\n\r\f\\']|\\{nl}|{escape})*\\? badcomment {badcomment1}|{badcomment2} badcomment1 \/\*[^*]*\*+([^/*][^*]*\*+)* badcomment2 \/\*[^*]*(\*+[^/*][^*]*)* baduri {baduri1}|{baduri2}|{baduri3} baduri1 url\({w}([!#$%&*-~]|{nonascii}|{escape})*{w} baduri2 url\({w}{string}{w} baduri3 url\({w}{badstring} nl \n|\r\n|\r|\f w [ \t\r\n\f]* 復制代碼

    CSS Syntax

    下面是css的 syntax 定義:

    stylesheet : [ CDO | CDC | S | statement ]*; statement : ruleset | at-rule; at-rule : ATKEYWORD S* any* [ block | ';' S* ]; block : '{' S* [ any | block | ATKEYWORD S* | ';' S* ]* '}' S*; ruleset : selector? '{' S* declaration? [ ';' S* declaration? ]* '}' S*; selector : any+; declaration : property S* ':' S* value; property : IDENT; value : [ any | block | ATKEYWORD S* ]+; any : [ IDENT | NUMBER | PERCENTAGE | DIMENSION | STRING| DELIM | URI | HASH | UNICODE-RANGE | INCLUDES| DASHMATCH | ':' | FUNCTION S* [any|unused]* ')'| '(' S* [any|unused]* ')' | '[' S* [any|unused]* ']'] S*; unused : block | ATKEYWORD S* | ';' S* | CDO S* | CDC S*; 復制代碼

    CSSOM Tree

    CSS解析得到的parse tree 經過加工之后,就得到了CSSOM Tree。 CSSOM 被稱為“css 對象模型”。

    CSSOM Tree 對外定義接口,可以通過js去獲取和修改其中的內容。開發(fā)者可以通過document.styleSheets的接口獲取到當前頁面中所有的css樣式表。

    CSSOM

    那么CSSOM 到底長什么樣子呢,我們下面來看一下:

    <!doctype html> <html lang="en"> <head><style>.test1 {color: red;}</style><style>.test2 {color: green;}</style><link rel="stylesheet" href="./test3.css"> </head> <body><div class="test1">TEST CSSOM1</div><div class="test2">TEST CSSOM2</div><div class="test3">TEST CSSOM3</div> </body> </html> 復制代碼

    上面的代碼在瀏覽器中打開,然后在控制臺里面輸入document.styleSheets,就可以打印出來CSSOM,如下圖所示:

    可以看到,CSSOM是一個對象,其中有三個屬性,均是 CSSStylelSheet 對象。CSSStylelSheet 對象用于表示每個樣式表。由于我們在document里面引入了一個外部樣式表,和兩個內聯樣式表,所以CSSOM對象中包含了3個CSSStylelSheet對象。

    CSSStyleSheet

    CSSStylelSheet對象又長什么樣子呢?如下圖所示:

    CSSStyleSheet 對象主要包括下面的屬性:

    • type

      字符串 “text/css”

    • href

      表示該樣式表的來源,如果是內聯樣式,則href值為null

    • parentStyleSheet

      父節(jié)點的styleSheet

    • ownerNode

      該樣式表所匹配到的DOM節(jié)點,如果沒有則為空

    • ownerRule

      父親節(jié)點的styleSheet中的樣式對本節(jié)點的合并

    • media

      該樣式表中相關聯的 MediaList

    • title

      style 標簽的title屬性,不常見

      <style title="papaya whip">body { background: #ffefd5; } </style> 復制代碼
    • disabled

      是否禁用該樣式表,可通過js控制該屬性,從而控制頁面是否應用該樣式表

    樣式表的解析

    瀏覽器的渲染引擎是從上往下進行解析的。

    當渲染引擎遇到 <style> 節(jié)點的時候,會立馬暫停解析html, 轉而解析CSS規(guī)則,一旦CSS規(guī)則解析完成,渲染引擎會繼續(xù)解析html

    當渲染引擎遇到 <link>節(jié)點的時候,瀏覽器的網絡組件會發(fā)起對 style 文件的請求,同時渲染引擎不會暫停,而是繼續(xù)往下解析。等到 style 文件從服務器傳輸到瀏覽器的時候,渲染引擎立馬暫停解析html, 轉而解析CSS規(guī)則,一旦CSS規(guī)則解析完成,渲染引擎會繼續(xù)解析html。

    可以聯想一下script的解析。

    當渲染引擎遇到 <script> 節(jié)點的時候,會立馬暫停解析html。

    如果這個 <script> 節(jié)點是內聯,則 JS 引擎會馬上執(zhí)行js代碼,同時渲染引擎也暫停了工作。什么時候等 JS 代碼執(zhí)行完了,什么時候渲染引擎重新繼續(xù)工作。如果JS 代碼執(zhí)行不完,那渲染引擎就繼續(xù)等著吧。

    如果這個 <script> 節(jié)點是外鏈的,瀏覽器的網絡組件會發(fā)起對 script 文件的請求,渲染引擎也暫停了執(zhí)行。什么時候等 JS 代碼下載完畢,并且執(zhí)行完了,什么時候渲染引擎重新繼續(xù)工作。

    在2011年的時候,瀏覽器廠商推出了“推測性”解析的概念。

    “推測性”解析就是,當讓渲染引擎干等著js代碼下載和運行的時候,會起一個第三個進程,繼續(xù)解析剩下的html。當js代碼下載好了,準備開始執(zhí)行js代碼的時候,第三個進程就會馬上發(fā)起對剩下html所引用的資源——圖片,樣式表和js代碼的請求。

    這樣就節(jié)省了之后加載和解析時間。

    被稱為“推測性”解析是因為,前面的js代碼存在一定的概率修改DOM節(jié)點,有可能會讓后面的DOM節(jié)點消逝,那么我們的工作就白費了。瀏覽器“推測”這樣的發(fā)生的概率比較小。

    讓渲染引擎干等著不工作是非常低效率的,所以雅虎軍規(guī)會讓把 script 標簽放在body的底部。

    言歸正傳,樣式表放在head的前邊,有兩個原因:

  • 盡快加載樣式表
  • 不要耽誤js代碼選擇dom節(jié)點
  • Render Tree

    當瀏覽器忙著構建DOM TreeCSSOM Tree的時候, 瀏覽器同時將兩者結合生成Render Tree。也就是說,瀏覽器構建DOM TreeCSSOM Tree ,和結合生成Render Tree,這兩個是同時進行的。

    Render Forest

    Levi Weintraub(webkit 的作者之一)在一次分享(分享的視頻點這里,分享的ppt點這里)中開玩笑說,準確的來說,我們大家提的Render Tree應該是Render Forest (森林)。因為事實上,存在多條Tree:

    • render object tree ( 稍后會詳細講解)
    • layer tree
    • inline box tree
    • style tee

    這里做一點說明。

    有很多其他的文章中提到了 Render Tree,其中的每一個構成的節(jié)點都是 Render Obejcts, 因此其他文章中的 Render Tree 概念,在本文中等同于 Render Object Tree ( Levi Weintraub 和 Webkit core 的叫法都是Render Object Tree, 其他文章中 Render Tree的本義也應是 Render Object Tree)。

    Render Object Tree 與 Dom Tree

    DOM TreeRender Object Tree 之間的關系是什么樣的?

    Render Object Tree 并不嚴格等于Dom Tree,先看一張DOM Tree 和 Render Object Tree的直觀的對比圖:

    上面左側DOM tree的節(jié)點對應右側Render Object Tree上的節(jié)點。細心的你會注意到,上圖左側的DOM Tree中的HTMLDivElement 會變成RenderBlock, HTMLSpanElement 會變成RenderInline,也就是說,DOM節(jié)點對應的 render object 節(jié)點并不一樣。

    DOM節(jié)點對應的 render object 節(jié)點并不一樣分這幾種情況:

  • display : none 的DOM 節(jié)點沒有對應的 Render Object Tree 的節(jié)點

    這里的display:none 屬性,有可能是我們在CSS里面設置的,也有可能是瀏覽器默認的添加的屬性。比如說下面的元素就會有默認的display:none的屬性。

    • <head>
    • <meta>
    • <link>
    • <script>
  • 一個DOM節(jié)點,可能有多個 Render Object Tree的節(jié)點
  • 下面的各個DOM元素,會對應多個Render Object Tree的節(jié)點

    • <input type="**color**">

    • <input type="**date**">

    • <input type="**file**">

    • <input type="**range**">

    • <input type="**radio**">

    • <input type="**checkbox**">

    • <select>

      比如說,<input type="range">?就會有兩個renderer:

  • 脫離了文檔流的DOM節(jié)點,DOM Tree 和 Render Object Tree 是對應不上的。
  • 脫離文檔流的情況,要么是float, 要么是position: absolute / fixed。

    比如說對于下面的結構:

    <body> <div><p>Lorem ipsum</p></div> </body> 復制代碼

    ? 它的 DOM tree和 Render Tree 如下圖所示:

    ? 如果增加脫離文檔流的樣式,如下:

    p {position: absolute; } 復制代碼

    ? 情況就會變成下面這樣:

    <p> 節(jié)點對應的 Render Tree 的節(jié)點,從父節(jié)點脫離出來,掛到了頂部的RenderView 節(jié)點下面。

    為什么脫離了文檔流的節(jié)點,在 Render Object Tree中的結構不同?脫離了文檔流的節(jié)點在構建Render Object Tree又是如何處理的?會在下面的內容中介紹。

    Render Object Tree 上的節(jié)點

    render object tree 是由 render object 節(jié)點構成的。render object 節(jié)點在不同的瀏覽器叫法不同,在webkit中被稱為 renderer, 或者 被稱為 render objects, 在firfox中,被稱為frames。

    render object 的節(jié)點的類是 RenderObject,定義在源碼的目錄webkit/Source/WebCore/rendering/RenderObject.h中。

    下面是RenderObject.h的簡化版本:

    // Credit to Tali Garsiel for this simplified version of WebCore's RenderObject.h class RenderObject {virtual void layout();virtual void paint(PaintInfo);virtual void rect repaintRect();Node* node; // 這個render tree的節(jié)點所指向的那個Dom節(jié)點RenderStyle* style; // 這個render tree節(jié)點的計算出來的樣式RenderLayer* containingLayer; // 包含這個 render tree 的 z-index layer } 復制代碼

    RenderBox?是RenderObject的一個子類,它主要是負責DOM樹上的每一個節(jié)點的盒模型。

    RenderBox?包括一些計算好尺寸的信息,比如說:

    • height
    • width
    • padding
    • border
    • margin
    • clientLeft
    • clientTop
    • clientWidth
    • clientHeight
    • offsetLeft
    • offsetTop
    • offsetWidth
    • offsetHeight
    • scrollLeft
    • scrollTop
    • scrollWidth
    • scrollHeight

    render object 的節(jié)點的作用如下:

    • 負責 layout 和 paint

    • 負責查詢DOM元素查詢尺寸API

      比如說獲取offsetHeight, offsetWidth的屬性

    render object 節(jié)點的類型

    我們在CSS中接觸過文檔流的概念,文檔流中的元素分為塊狀元素和行內元素,比如說div是塊狀元素,span是行內元素。塊狀元素和行內元素在文檔流中的表現不同,就是在這里決定的。

    Render Object 的節(jié)點類型分為下面幾種:

  • RenderBlock

    display: block 的DOM節(jié)點對應的render object節(jié)點類型為RenderBlock

  • RenderInline

    display:inline 的DOM節(jié)點對應的render object節(jié)點類型為RenderInline

  • RenderReplaced

    可能我們之前聽說過“替換元素” 的概念,比如說常見的“替換元素”有下面:

    • <iframe>
    • <video>
    • <embed>
    • <img>

    為啥被稱為“替換元素”,是因為他們的內容會被一個獨立于HTML/CSS上下文的外部資源所替代。

    “替代元素” 的DOM節(jié)點對應的render object 節(jié)點類型為RenderReplaced

  • RenderTable

    <table> 元素的DOM節(jié)點對應的render object 節(jié)點類型為 RenderTable

  • RenderText

    文本內容的DOM節(jié)點對應的render object 的節(jié)點類型為 RenderText

  • 源碼大概長這個樣子:

    RenderObject* RenderObject::createObject(Node* node, RenderStyle* style) {Document* doc = node->document();RenderArena* arena = doc->renderArena();...RenderObject* o = 0;switch (style->display()) {case NONE:break;case INLINE:o = new (arena) RenderInline(node);break;case BLOCK:o = new (arena) RenderBlock(node);break;case INLINE_BLOCK:o = new (arena) RenderBlock(node);break;case LIST_ITEM:o = new (arena) RenderListItem(node);break;...}return o; } 復制代碼

    上面5中類型的Render Object 的節(jié)點之間的關系組合并不是沒有準則的,在我們寫出嵌套不規(guī)范的HTML時,渲染引擎幫我們做了很多事情。

    Anonymous renderers

    render object tree 遵守2個準則:

    • 在文檔流中的塊狀元素的子節(jié)點,要么都是塊狀元素,要么都是行內元素。
    • 在文檔流中的行內元素的子節(jié)點,只能都是行內元素。

    anonymounse renderers(匿名的render object 節(jié)點)就是用于處理不遵守這兩種規(guī)則的代碼的,如果出現不符合這兩個準則的情況,比如說下面:

  • 若在塊狀元素里面同時出現了塊狀元素和行內元素:
  • <div>Some text<div>Some more text</div> </div> 復制代碼

    上面的代碼中,最外層的div節(jié)點有兩個子節(jié)點,第一個子節(jié)點是行內元素,第二個子節(jié)點是塊狀元素。render object tree 中會構建一個anonymounse renderer去包裹 text 節(jié)點,因此上面的代碼變成了下面的:

    <div><anonymous block>Some text</anonymous block><div>Some more text</div> </div> 復制代碼
  • 還有另外一種非常糟糕的情況,就是在行內元素中出現了塊狀元素:
  • <i>Italic only <b>italic and bold<div>Wow, a block!</div><div>Wow, another block!</div> More italic and bold text</b> More italic text</i>復制代碼

    上面的代碼中,render object tree需要做更多的事情去修復這種糟糕的DOM tree: 三個anonymounse renderers會被創(chuàng)建,上面的代碼會被分割成三段,被三個匿名的block 包裹。

    <anonymous pre block><i>Italic only <b>italic and bold</b></i> </anonymous pre block><anonymous middle block><div>Wow, a block!</div><div>Wow, another block!</div> </anonymous middle block><anonymous post block><i><b>More italic and bold text</b> More italic text</i> </anonymous post block>復制代碼

    注意到,<i> 元素和 <b> 元素都被分割進了<anonymous pre block> 和 <anonymous post block> 兩個類型為 RenderBlock 的節(jié)點中,他們通過一種叫*continuation chain(延續(xù)鏈)*的機制來鏈接。

    負責上面遞歸拆分行內元素的生產*continuation chain(延續(xù)鏈)*的方法被稱為 splitFlow。

    因此,一旦你寫出了不符合規(guī)范的html結構, 在構建render object tree時就需要更多的工作去糾正,從而造成頁面性能的下降。

    構建 Render Object Tree

    GeckoWebKit 采用了不同的方案來構建 Render Tree

    Gecko 是把樣式計算和構建Render Object Tree 的工作代理到 FrameConstructor 對象上。而 webkit 采用的方案是,每一個DOM節(jié)點自己計算自己的樣式,并且構建自己 的Render object tree 對應的節(jié)點。

    Gecko 針對DOM的更新增加了一個 listener,當DOM 更新的時候,更新的DOM節(jié)點被傳到一個指定的對象FrameConstructor, 這個FrameConstructor 會為 DOM 節(jié)點計算樣式,同時為這個DOM節(jié)點創(chuàng)建一個合適的 Render Object Tree節(jié)點。

    WebKit構建 Render Object tree 的過程被稱為attachment, 每一個DOM節(jié)點被賦予一個 attach() 方法,這是一個同步的方法,當每一個DOM節(jié)點被插入DOM樹的時候, 該DOM節(jié)點的attach()方法就會被調用。

    樣式計算

    在構建Render Object Tree的時候,需要進行樣式計算,也就是Render Tree每一個節(jié)點都需要有一個visual information的信息,才可以被繪制在屏幕上,這就需要樣式計算這一過程。

    而樣式計算需要兩部分“原材料”:

  • DOM Tree
  • 一堆樣式規(guī)則
  • DOM Tree在HTML解析之后就可以拿到了,一堆樣式規(guī)則可以來自下面:

    • 瀏覽器默認的樣式
    • 外鏈樣式
    • 內聯樣式
    • DOM節(jié)點上的style屬性

    那么樣式規(guī)則是如何構成的呢?

    • 樣式表是一堆 規(guī)則(rules)的集合;
    • 當然也不光都是 規(guī)則(rules), 還會有一些奇怪的東西:@import, @media, @namespace 等等
    • 一個**規(guī)則(rules)是由選擇器(selector)聲明塊(declaration block)**構成的
    • **聲明塊(declaration block)由一堆聲明(declaration)**加中括號構成
    • **聲明(declaration ** 由 property 和 value 構成。

    樣式計算存在以下三個難點:

  • style 樣式數據太多,會占用大量內存
  • 匹配元素會影響性能
  • css規(guī)則的應用順序
  • 下面我們介紹這個三個難點是如何解決的。

    樣式規(guī)則的應用順序

    某一個DOM節(jié)點上可能有多個規(guī)則,比如下下面:

    div p {color: goldenrod; } p {color: red; }復制代碼

    那么這個DOM節(jié)點究竟用的是哪個規(guī)則?

    規(guī)則的權重是:先看 order , 然后再算specificity, 最后再看哪個規(guī)則靠的更近。

    order

    order的權重從高到底:

  • 用戶的 ! important 聲明(瀏覽器可以讓用戶導入自定的樣式)
  • 程序員寫的 ! important 聲明
  • 程序員寫的普通css樣式
  • 瀏覽器的默認css樣
  • Specificity

    Specificity是一個相加起來的值

    #foo .bar > [name="baz"]::first-line {} /* Specificity: 0 1 2 1 */復制代碼
  • 第一位的數值(a)

    是否有DOM節(jié)點上style屬性的值,有則是1,否則是0

  • 第二位的數值 (b)

    id選擇器的數量之和

  • 第三位的數值 (c)

    class選擇器,屬性選擇器,偽類選擇器個數之和

  • 第四位的數值 (d)

    標簽選擇器,偽元素選擇器個數之和

  • 下面是例子:

    * {} /* a=0 b=0 c=0 d=0 -> specificity = 0,0,0,0 */li {} /* a=0 b=0 c=0 d=1 -> specificity = 0,0,0,1 */li:first-line {} /* a=0 b=0 c=0 d=2 -> specificity = 0,0,0,2 */ul li {} /* a=0 b=0 c=0 d=2 -> specificity = 0,0,0,2 */ul ol+li {} /* a=0 b=0 c=0 d=3 -> specificity = 0,0,0,3 */h1 + *[rel=up]{} /* a=0 b=0 c=1 d=1 -> specificity = 0,0,1,1 */ul ol li.red {} /* a=0 b=0 c=1 d=3 -> specificity = 0,0,1,3 */li.red.level {} /* a=0 b=0 c=2 d=1 -> specificity = 0,0,2,1 */#x34y {} /* a=0 b=1 c=0 d=0 -> specificity = 0,1,0,0 */style="" /* a=1 b=0 c=0 d=0 -> specificity = 1,0,0,0 */復制代碼

    style數據太多,占用大量內存

    這里的style數據太多,不是說我們寫的css樣式太多,而是Render Object Tree每一個節(jié)點上都需要存儲全部的CSS樣式,那些沒有被指定的樣式,其值為繼承父節(jié)點的樣式,或者是瀏覽器的默認樣式,或者干脆是個空值。

    webkit 和 gecko 采用了不同的解決方案。

    webkit:共享樣式數據

    WebKit 的解決方案是,節(jié)點們會引用RenderStyle對象。這些對象在以下情況下可以由不同的DOM節(jié)點共享,從而節(jié)省空間和提高性能。

    • 這些節(jié)點是同級關系
    • 這些節(jié)點有相同的偽類狀態(tài):hover、:active、:focus
    • 這些節(jié)點都沒有id
    • 這些節(jié)點有相同的tag名稱
    • 這些節(jié)點有相同的class
    • 這些節(jié)點都沒有通過style屬性設置的樣式
    • 這些節(jié)點沒有一個是使用 兄弟選擇器的,比如說: div + p,?div ~ p,?:last-child,?:first-child,?:nth-child(),?:nth-of-type()
    Gecko:style struct sharing

    Gecko 采用了一種 style struct sharing 的機制。有一些css屬性可以聚合在一起,比如說font-size, font-family, color 等等,瀏覽器就把這些可以被劃分為一組的屬性,單獨的保存到一個對象里面,這個對象被稱為 style struct。如下表所示:

    上圖中的,computed style 里就不用存儲CSS全部的200多個屬性,而是保存著對這些 style struct對象的引用。

    這樣一來,一些具有相同屬性的DOM 節(jié)點就可以引用相同的 style struct, 不僅如此,因為子節(jié)點有一些屬性會繼承父節(jié)點,那么保存這些屬性的 style struct 就會被父節(jié)點和子節(jié)點所共享。

    匹配元素會影響性能

    對于每一個DOM節(jié)點,css引擎需要去遍歷所有的css規(guī)則看是否匹配。對于大部分的DOM節(jié)點,css規(guī)則的匹配并不會發(fā)生改動。

    比如說,用戶把鼠標hover到一個父元素上面,這個父元素的css規(guī)則匹配是發(fā)生了變化,我們不僅僅要重新計算這個父元素的樣式,還需要重新計算這個父元素的子元素的樣式(因為要處理繼承的樣式),但是能匹配這些子元素的樣式規(guī)則,是不會變的。

    如果我們能記下來,有哪些selector可以匹配到就好了。

    為了優(yōu)化這一點,CSS 引擎在進行 selector 匹配時,會根據權重的順序把他們排成一串,然后把這一串加到右邊的 CSS rule tree 上面。

    CSS引擎希望右邊CSS rule tree 的分支數越少越好,因此會將新加入的一串盡量的合并到已有的分支,所以上面的過程會是下面這樣的:

    然后遍歷每一個DOM節(jié)點去找能匹配到的CSS Rule Tree的分支,從CSS rule Tree的底部開始,一路向上開始匹配,直到找到對應的那一條 style rule Tree分支。這就是人們口中常說的,css選擇器是從右邊匹配的。

    當瀏覽器因為某種原因(用戶交互,js修改DOM)進行重新渲染的時候,CSS引擎會快速檢查一下,對父節(jié)點的改動是否會影響到子節(jié)點的 selector 匹配,如果不影響,CSS引擎就直接拿到每一個子節(jié)點對CSS rule Tree 對應那個分支的指針,節(jié)省掉匹配選擇器的時間。

    盡管如此,我們還是需要在第一次遍歷每一個DOM節(jié)點去找到對應的CSS Rule Tree的分支。如果我們有10000個相同的節(jié)點,就需要遍歷10000次。

    Gecko 和 Webkit 都對此進行了優(yōu)化,在遍歷完一個節(jié)點之后,會把計算好的樣式放到緩存中,在遍歷下一個節(jié)點之前,會做一個判斷,看是否可以復用緩存中的樣式。

    這個判斷包括一下幾點:

  • 兩個節(jié)點是否有相同的id, class

  • 兩個節(jié)點是否有相同的style 屬性

  • 兩個節(jié)點對應的父親節(jié)點是否共享一份計算好的樣式,那該兩個節(jié)點繼承的樣式也是相同的。

  • 解析階段如何優(yōu)化

    更加符合規(guī)范的html結構

    上面在構建render object tree 的過程中,會額外做很多工作處理我們不符合規(guī)范的DOM 結構,比如說,調用splitflow 方法分割代碼,用 anonymous renderBlock 包裹不符合規(guī)范的節(jié)點。

    之前我們都聽過建議:“要編寫更有語義,更符合規(guī)范的html結構“,原因就在于此,可以讓渲染引擎做更少的事情。

    下面是模擬一種不不符合規(guī)范的情況:

    <i v-for="n in 1000">Italic only<b>italic and bold<div>Wow, a block!</div><div>Wow, another block!<b>More italic and bold text</b><div>More italic and bold text<p>More italic and bold text</p></div></div>More italic and bold textMore italic and bold text</b> More italic text</i>復制代碼

    在控制臺里面,設置cpu 為6x slowdown,然后記錄渲染數據如下:

    其中花費了 12888ms 進行了rendering 過程。

    如果我們對html代碼僅僅做幾處修改,在不考慮css優(yōu)化、樣式優(yōu)化的前提下:

    <div v-for="n in nums"><p>Italic only</p><div>italic and bold<div>Wow, a block!</div><div>Wow, another block!<b>More italic and bold text</b><div>More italic and bold text<p>More italic and bold text</p></div></div>More italic and bold textMore italic and bold text</div></div>復制代碼

    在控制臺里面,設置cpu 為6x slowdown,然后記錄渲染數據如下:

    可以發(fā)現,render 階段的渲染時間為11506ms,rendering 階段渲染的時間相比于12888ms減少了1382ms,時間縮短了12%

    一次測量可能有誤差,但無論進行多次測量,都會發(fā)現第二種的代碼的渲染時間要小于第一種代碼的渲染時間。

    選擇器的優(yōu)化

    不同的選擇器,匹配的效率會有差距,但是差距不大。

    我們用一個有1000個DOM節(jié)點的頁面來測試,分別在5個瀏覽器中嘗試以下20種匹配器:

    1. Data Attribute (unqualified)*/[data-select] {color: red;}/*2. Data Attribute (qualified)a[data-select] {color: red;}*//*3. Data Attribute (unqualified with value)[data-select="link"] {color: red;}*//*4. Data Attribute (qualified with value)a[data-select="link"] {color: red;}*//*5. Multiple Data Attributes (qualified with values)div[data-div="layer1"] a[data-select="link"] {color: red;}*//*6. Solo Pseudo selectora:after {content: "after";color: red;}*//*7. Combined classes.tagA.link {color: red;}*//*8. Multiple classes .tagUl .link {color: red;}*//*9. Multiple classes (using child selector).tagB > .tagA {color: red;}*//*10. Partial attribute matching[class^="wrap"] {color: red;} *//*11. Nth-child selector.div:nth-of-type(1) a {color: red;}*//*12. Nth-child selector followed by nth-child selector.div:nth-of-type(1) .div:nth-of-type(1) a {color: red;}*//*13. Insanity selection (unlucky for some)div.wrapper > div.tagDiv > div.tagDiv.layer2 > ul.tagUL > li.tagLi > b.tagB > a.TagA.link {color: red;}*//*14. Slight insanity.tagLi .tagB a.TagA.link {color: red;}*//*15. Universal* {color: red;}*//*16. Element singlea {color: red;}*//*17. Element doublediv a {color: red;}*//*18. Element treblediv ul a {color: red;}*//*19. Element treble pseudodiv ul a:after; {content: "after";color: red;}*//*20. Single class.link {color: red;} 復制代碼

    測試的結果如下:

    TestChrome 34Firefox 29Opera 19IE9Android 4
    156.8125.463.6152.61455.2
    255.4128.461.41411404.6
    355125.661.8152.41363.4
    454.812963.2147.41421.2
    555.4124.463.2147.41411.2
    660.613858.41621500.4
    751.2126.656.8147.81453.8
    848.8127.456.2150.21398.8
    948.8127.455.8154.61348.4
    1052.2129.4581721420.2
    1149127.456.6148.41352
    1250.6127.258.4146.21377.6
    1364.6129.272.4152.81461.2
    1450.2129.854.8154.61381.2
    1550126.256.8154.81351.6
    1649.2127.656149.21379.2
    1750.4132.455157.61386
    1849.2128.858.6154.21380.6
    1948.6132.454.8148.41349.6
    2050.412855149.81393.8
    Biggest Diff.1613.617.631152
    Slowest13613106

    解釋

    在瀏覽器的引擎內部,這些選擇器會被重新的拆分,組合,優(yōu)化,編譯。而不同的瀏覽器內核采用不同的方案,所以幾乎沒有辦法預測,選擇器的優(yōu)化究竟能帶來多少收益。

    結論:

    合理的使用選擇器,比如說層級更少的class,的確會提高匹配的速度,但是速度的提高是有限的 。

    如果你通過dev tool 發(fā)現匹配選擇器的確是瓶頸,那么就選擇優(yōu)化它。

    精簡沒有用的樣式代碼

    大量無用代碼會拖慢瀏覽器的解析速度。

    用一個3000行的無用css樣式表和1500行的無用樣式表,進行測試:

    TestChrome 34Firefox 29Opera 19IE9Android 4
    300064.4237.674.2436.81714.6
    150051.6142.865.4358.61412.4

    對于火狐來說,在其他環(huán)節(jié)一致的情況下,頁面渲染的速度幾乎提升了一倍

    盡管現在的慣例是把css 打包成一個巨大單一的css文件。這樣做的確是有好處的,減少http請求的數量。但是拆分css文件可以讓加載速度更快,瀏覽器的解析速度更快。

    這一項的優(yōu)化是非常顯著的,通常可以省下來 2ms ~ 300ms的時間。

    精簡的過程可以使用uncss 工具來自動化的完成。

    瀏覽器渲染第二步:layout

    在上一節(jié)我們提到了 render object treerender object 的節(jié)點第一次被創(chuàng)建然后添加到 render object tree時,它身上沒有關于位置和尺寸的信息。接下來,確定每一個render object的位置和尺寸的過程被稱為layout。

    我們能在不同的文章中看到不同的名詞: 布局 ,layout , 回流 , reflow , 這些名詞說的都是一回事,不同瀏覽器的叫法不同。

    每一個renderer節(jié)點 都有l(wèi)ayout 方法。 在構建renderer節(jié)點的時候就聲明了這個方法:

    class RenderObject {virtual void layout();virtual void paint(PaintInfo);virtual void rect repaintRect();Node* node; // 這個render tree的節(jié)點所指向的那個Dom節(jié)點RenderStyle* style; // 這個render tree節(jié)點的計算出來的樣式RenderLayer* containingLayer; // 包含這個 render tree 的 z-index layer } 復制代碼

    layout ()是一個遞歸的過程。layout 過程究竟是誰來負責的呢? 一個名為 FrameView 的 class。

    FrameView 可以運行下面兩種類型的 layout :

  • 全局layout

    render tree 的根節(jié)點自身的layout方法被調用,然后整個render tree 被更新。

  • 局部layout

    只是區(qū)域性的更新,只適用于某個分支的改動不會影響到周圍的分支。

    目前局部layout只會在 text 更新的時候使用

  • Dirty Bits

    在layout 階段,采用一種稱為 Dirty Bits 的機制去判斷一個節(jié)點是否需要layout。當一個新的節(jié)點被插到tree中時,它不僅僅“弄臟“了它自身,還“弄臟“了相關的父節(jié)點(the relevant ancestor chain,下面會介紹)。有沒有被“弄臟”是通過設置bits (set bits)來標識的。

    bool needsLayout() const { return m_needsLayout || m_normalChildNeedsLayout || m_posChildNeedsLayout; } 復制代碼

    上面 needsLayout 為 true 有三種情況:

  • selfNeedsLayout

    Rederer 自身是 “臟”的。當一個 rederer 自身被設置為“臟”的,它相關的父親節(jié)點也會被設置一個標識來指出它們有一個“臟”的子節(jié)點

  • posChildNeedsLayout

    設置了postion不為static的子節(jié)點被弄臟了

  • normalChildNeedsLayout

    在文檔流中的子節(jié)點被弄臟了

  • 上面之所以要區(qū)分子節(jié)點是否在文檔流中,是為了layout過程的優(yōu)化。

    Containing Block (包含塊)

    上面提到了相關父節(jié)點(the relevant ancestor chain),那么究竟是如何判斷哪個節(jié)點是 **相關父節(jié)點 **?

    答案就是通過 containing block.

    Container Block(包含塊) 身份有兩個

  • 子節(jié)點的相關的父節(jié)點

  • 子節(jié)點的相對坐標系

    子節(jié)點都有 XPos 和 YPos 的坐標,這些坐標都是相對于他們的Containing Block (包含塊)而言的。

  • 下面介紹Container Block(包含塊) 概念。

    包含塊的定義

    通俗來講,Container Block 是決定子節(jié)點位置的父節(jié)點。每個子節(jié)點的位置都是相對于其container block來計算的。更詳細的信息可以點這個 css2.1 官方的解釋點這里

    有一種特殊的containing block —— initial containing block (最初的container block)。

    當Docuement 節(jié)點上的 renderer() 方法被調用時,會返回一個節(jié)點對象為render tree 的根節(jié)點,被稱作 RenderView, RenderView 對應的containing bock 就是 initial containing block。

    initial containing block 的尺寸永遠是viewport的尺寸,且永遠是相對于整個文檔的 position(0,0) 的位置。下面是圖示:

    黑色的框代表的是 initial containing block (最初的container block) , 灰色的框表示整個 document。當document往下滾動的時候, initial containing block (最初的container block) 就會被移出了屏幕。 initial containing block (最初的container block) 始終在document 的頂部,并且大小始終是 viewport 的尺寸。

    那么render Tree上的節(jié)點,它們各自的 containing block 是什么?

    • 根節(jié)點的 containing block 始終是 RenderView

    • 如果一個renderer節(jié)點的css postion 的值為 relative 或 static,則其 containing block 為最近的父節(jié)點

    • 如果一個renderer節(jié)點的css postion 的值為 absolute, 則其containing block 為最近的 css postion 的值不為static 的父節(jié)點。如果這樣的父節(jié)點不存在,則為 RenderView,也就是根節(jié)點的containing block

    • 如果一個renderer節(jié)點的css postion 的值為 fixed。這個情況有一些特殊,因為 W3C 標準和 webkit core 介紹的不一樣。W3C 最新的標準認為css postion 的值為 fixed的renderer節(jié)點的containing block是viewport ,原文如下:

      而webkit core 認為css postion 的值為 fixed的renderer節(jié)點的containing block是RenderView。RenderView并不會表現的和viewport一樣,但是RenderView會根據頁面滾動的距離算出css postion 的值為 fixed的renderer節(jié)點的位置。這是因為單獨為viewport 生成一個renderer 節(jié)點并不簡單。原文如下:

    render tree 有兩個方法用來判斷 renderer 的position:

    bool isPositioned() const; // absolute or fixed positioning bool isRelPositioned() const; // relative positioning復制代碼

    render tree 有一個方法用來獲取某一個塊狀 rederer 的containing block(相對父節(jié)點)

    RenderBlock* containingBlock() const復制代碼

    render tree 還有一個方法是兼容了行內元素獲取相對父節(jié)點的方法,用來代替containingBlock (因為containingBlock只適用于塊狀元素)

    RenderObject* container() const復制代碼

    當一個 renderer 被標記為需要 layout的時候,就會通過container()找到相對父節(jié)點,把isPositioned 的狀態(tài)傳遞給相對父節(jié)點。如果 renderer 的position是absolute 或 fixed ,則相對父節(jié)點的posChildNeedsLayout為true,如果 renderer的position 是 static 或 relative , 則相對父節(jié)點的 normalChildNeedsLayout 為 true。

    會觸發(fā)layout 的屬性

  • 盒子模型相關的屬性

    • width

    • height

    • padding

    • margin

    • border

    • display

    • ……
  • 定位屬性和浮動

    • top
    • bottom
    • left
    • right
    • position
    • float
    • clear
  • 節(jié)點內部的文字結構

    • text - aligh
    • overflow
    • font-weight
    • font- family
    • font-size
    • line-height
  • 上面只是一部分,更全部的可以點擊 csstriggers 來查閱;

    csstrigger 里面需要注意的有幾點。

  • opacity的改動,在blink內核和Gecko內核上不會觸發(fā)layout 和 repaint

  • transform的改動,在blink內核和Gecko內核上不會觸發(fā)layout 和 repaint

  • visibility 的改動,在Gecko 內核上不會觸發(fā) layout repaint, 和 composite

  • 會觸發(fā)layout 的方法

    幾乎任何測量元素的寬度,高度,和位置的方法都會不可避免的觸發(fā)reflow, 包括但是不限于:

    • elem.getBoundingClientRect()
    • window.getComputedStyle()
    • window.scrollY
    • and a lot more…

    如何避免重復Layout

    不要頻繁的增刪改查DOM

    不要頻繁的修改默認根字體大小

    不要一條條去修改DOM樣式,而是通過切換className

    雖然切換className 也會造成性能上的影響,但是次數上減少了。

    “離線”修改DOM

    比如說一定要修改這個dom節(jié)點100次,那么先把dom的display設置為 none ( 僅僅會觸發(fā)一次回流 )

    使用flexbox

    老的布局模型以相對/絕對/浮動的方式將元素定位到屏幕上 Floxbox布局模型用流式布局的方式將元素定位到屏幕上,flex性能更好。

    不要使用table

    使用table布局哪怕一個很小的改動都會造成重新布局

    避免強制性的同步layout

    layout根據區(qū)域來劃分的,分為全局性layout, 和局部的layout。比如說修改根字體的大小,會觸發(fā)全局性layout。

    全局性layout是同步的,會立刻馬上被執(zhí)行,而局部性的layout是異步的,分批次的。瀏覽器會嘗試合并多次局部性的layout為一次,然后異步的執(zhí)行一次,從而提高效率。

    但是js一些操作會觸發(fā)強制性的同步布局,從而影響頁面性能,比如說讀取 offsetHeight、offsetWidth 值的時候。

    瀏覽器渲染第三步:paint

    第三個階段是paint 階段

    會觸發(fā)paint 的屬性

    • color
    • border - style
    • border - radius
    • visibility
    • Text -decoration
    • background
    • background
    • Background - image
    • background - size
    • Background - repeat
    • background - position
    • outline - color
    • outline
    • outline - style
    • outline - width
    • box - shadow

    如何優(yōu)化

    使用transform代替top, left 的變化

    使用transform不會觸發(fā)layout , 只會觸發(fā)paint。

    如果你想頁面中做一些比較炫酷的效果,相信我,transform可以滿足你的需求。

    // 位置的變換 transform: translate(1px,2px)// 大小的變換 transform: scale(1.2)復制代碼

    使用opacity 來代替 visibility

    因為 visibility屬性會觸發(fā)重繪,而opacity 則不會觸發(fā)重繪

    避免使用耗性能的屬性

    可以點擊這個鏈接進行測試測試連接

    .link {background-color: red;border-radius: 5px;padding: 3px;box-shadow: 0 5px 5px #000;-webkit-transform: rotate(10deg);-moz-transform: rotate(10deg);-ms-transform: rotate(10deg);transform: rotate(10deg);display: block; }復制代碼

    測試結果:

    TestChrome 34Firefox 29Opera 19IE9Android 4
    Expensive Styles65.2151.465.2259.21923

    需要注意的是,高耗css樣式如果不會頻繁的觸發(fā)回流和重繪,只會在頁面渲染的時候被執(zhí)行一次,那么對頁面的性能影響是有限的。如果頻繁的觸發(fā)回流和重繪,那么最基本的css樣式也會影響到頁面的性能。

    那么哪些 css 樣式會造成頁面性能的問題呢?

    • Border-radius
    • Shadow
    • gradients
    • transform rotating

    更多的內容請參考 連接

    瀏覽器渲染第四步:composite

    什么是合成層

    上面幾個階段可以用下面一張圖來表示:

    1. 從 Nodes 到 LayoutObjects

    DOM 樹每個 Node 節(jié)點都有一個對應的 LayoutObject 。LayoutObject 知道如何在屏幕上 paint Node 的內容。

    2. 從 LayoutObjects 到 PaintLayers

    有相同坐標的 LayoutObjects ,在同一個PaintLayer內。 根據創(chuàng)建PaintLayer 的原因不同,可以將其分為常見的 3 類:

  • NormalPaintLayer
    • 根元素
    • relative、fixed、sticky、absolute
    • opacity 小于 1
    • CSS 濾鏡(fliter)
    • 有 CSS mask 屬性
    • 有 CSS mix-blend-mode 屬性(不為 normal)
    • 有 CSS transform 屬性(不為 none)
    • backface-visibility 屬性為 hidden
    • 有 CSS reflection 屬性
    • 有 CSS column-count 屬性(不為 auto)或者 有 CSS column-width 屬性(不為 auto)
    • 當前有對于 opacity、transform、fliter、backdrop-filter 應用動畫
  • OverflowClipPaintLayer
    • overflow 不為 visible
  • NoPaintLayer
    • 不需要 paint 的 PaintLayer,比如一個沒有視覺屬性(背景、顏色、陰影等)的空 div。

    4. 從 PaintLayers 到 GraphicsLayers

    某些特殊的paintLayer會被當成合成層,合成層擁有單獨的 GraphicsLayer,而其他不是合成層的paintLayer,則和其第一個擁有GraphicsLayer 父層公用一個。

    每個 GraphicsLayer 都有一個GraphicsContextGraphicsContext 會負責輸出該層的位圖,位圖是存儲在共享內存中,作為紋理上傳到 GPU 中,最后由 GPU 將多個位圖進行合成,然后 draw 到屏幕上,此時,我們的頁面也就展現到了屏幕上。

    渲染層提升為合成層的原因

    渲染層提升為合成層的原因有一下幾種:

    • 直接原因
      • 硬件加速的 iframe 元素(比如 iframe 嵌入的頁面中有合成層
      • video元素
      • 3d transiform
      • 在 DPI 較高的屏幕上,fix 定位的元素會自動地被提升到合成層中。但在 DPI 較低的設備上卻并非如此
      • backface-visibility 為 hidden
      • 對 opacity、transform、fliter、backdropfilter 應用了 animation 或者 transition(需要注意的是 active 的 animation 或者 transition,當 animation 或者 transition 效果未開始或結束后,提升合成層也會失效)
      • will-change 設置為 opacity、transform、top、left、bottom、right(其中 top、left 等需要設置明確的定位屬性,如 relative 等)
    • 后代元素原因
      • 有合成層后代同時本身有 transform、opactiy(小于 1)、mask、fliter、reflection 屬性
      • 有合成層后代同時本身 overflow 不為 visible(如果本身是因為明確的定位因素產生的 SelfPaintingLayer,則需要 z-index 不為 auto)
      • 有合成層后代同時本身 fixed 定位
      • 有 3D transfrom 的合成層后代同時本身有 preserves-3d 屬性
      • 有 3D transfrom 的合成層后代同時本身有 perspective 屬性
    • overlap 重疊原因

    為啥overlap 重疊也會造成提升合成層渲染? 圖層之間有重疊關系,需要按照順序合并圖層。

    如何優(yōu)化

    如果把一個頻繁修改的dom元素,抽出一個單獨的圖層,然后這個元素的layout, paint 階段都會在這個圖層進行,從而減少對其他元素的影響。

    使用will-change 或者 transform3d

    使用 will-change 或者 transform3d

    1. will-change: transform/opacity2. transform3d(0,0,0,)復制代碼

    使用加速視頻解碼的節(jié)點

    因為視頻中的每一幀都是在動的,所以視頻的區(qū)域,瀏覽器每一幀都需要重繪。所以瀏覽器會自己優(yōu)化,把這個區(qū)域的給抽出一個單獨的圖層

    擁有3D(webgl) 上下文或者加速的2D上下文的節(jié)點

    混合插件(flash)

    如果某一個元素,通過z-index在復合層上面渲染,則該元素也會被提升到復合層

    需要注意的是,gif 圖片雖然也變化很頻繁,但是 img 標簽不會被單獨的提到一個復合層,所以我們需要單獨的提到一個獨立獨立的圖層之類。

    composite更詳盡的知識可以了解下面這個博客: 《GPU Accelerated Compositing in Chrome》

    頁面性能優(yōu)化實踐

    Bounce-btn優(yōu)化

    bounce-btn是類似于下面這種的:

    如果想實現這種效果,假設不考慮性能問題,寫出下面的代碼話:

    <div class="content-box"></div><div class="content-box"></div><div class="content-box"></div><div class="bounce-btn"></div><div class="content-box"></div><div class="content-box"></div><div class="content-box"></div>復制代碼.bounce-btn {width: 200px;height: 50px;background-color: antiquewhite;border-radius: 30px;margin: 10px auto;transition: all 1s; } .content-box {width: 400px;height: 200px;background-color: darkcyan;margin: 10px auto; }復制代碼let btnArr = document.querySelectorAll('.bounce-btn'); setInterval(() => {btnArr.forEach((dom) => {if ( dom.style.width ==='200px') {dom.style.width = '300px';dom.style.height = '70px';} else {dom.style.width = '200px';dom.style.height = '50px';}}) },2000)復制代碼

    可以發(fā)現這樣的性能是非常差的,我們打開dev-tool的paint flashing, 發(fā)現重新渲染的區(qū)域如綠色的區(qū)域所示:

    而此時的性能是,1000ms 的時間內,layout階段花費了29.9ms占了18.6%

    這個其實有兩個地方,第一是,bounce btn 這個元素被js 修改了width 、height 這些屬性,從而觸發(fā)了自身layout ——> repaint ——> composite。第二是,bounce btn 沒有脫離文檔流,它自身布局的變化,影響到了它下面的元素的布局,從而導致下面元素也觸發(fā)了layout ——> repaint ——> composite。

    那么我們把修改width, 改為 tansform: scale()

    let btnArr = document.querySelectorAll('.bounce-btn'); setInterval(() => {btnArr.forEach((dom) => {if ( dom.style.transform ==='scale(0.8)') {dom.style.transform = 'scale(2.5)';} else {dom.style.transform = 'scale(0.8)';}}) },2000)復制代碼

    頁面性能得到了提高:

    重新渲染的區(qū)域只有它自身了。此時的性能是,1000ms 的時間內,沒有存在layout階段,

    如果繼續(xù)優(yōu)化,我們通過aimation動畫來實現bounce的效果:

    @keyframes bounce {0% {transform: scale(0.8);}25% {transform: scale(1.5);}50% {transform: scale(1.5);}75% {transform: scale(1.5);}100% {transform: scale(0.8);}}復制代碼

    頁面中沒有重新渲染的區(qū)域:

    并且頁面性能幾乎沒有受到任何影響,不會重新經歷 layout ——> repaint ——> composite.

    所以,對于這種動效,優(yōu)先選擇 CSS animation > transform 修改 scale > 絕對定位 修改width > 文檔流中修改width

    跑馬燈的優(yōu)化

    跑馬燈的動效是:每隔3秒進行向左側滑動淡出,然后再滑動重新淡入,更新文本為“**砍價9元”

    之前的滑動和淡出的效果是通過vue提供的 <transision> 來實現的

    <transision> 原理

    當我們想要用到過渡效果,會在vue中寫這樣的代碼:

    <transition name="toggle"><div class="test"> </transition>復制代碼

    但是其實渲染到瀏覽器中的代碼,會依次是下面這樣的:

    // 過渡進入開始的一瞬間 <div class="test toggle-enter">// 過渡進入的中間階段 <div class="test toggle-enter-active">// 過渡進入的結束階段 <div class="test toggle-enter-active toggle-enter-to">// 過渡淡出開始的一瞬間 <div class="test toggle-leave">// 過渡淡出的中間階段 <div class="test toggle-leave-active">// 過渡淡出的結束階段 <div class="test toggle-leave-active toggle-leave-to">復制代碼

    也就是說,過渡效果的實現,是通過不停的修改、增加、刪除該dom節(jié)點的class來實現。

    <transision> 影響頁面性能

    一方面, v-if 會修改dom節(jié)點的結構,修改dom節(jié)點會造成瀏覽器重走一遍 layout 階段,也就是重排。另一方面,dom節(jié)點的class被不停的修改,也會導致瀏覽器的重排現象,因此頁面性能會比較大的受到影響。

    若頁面中 <transition> 控制的節(jié)點過多時,頁面的性能就會比較受影響。

    為了證明,下面代碼模擬了一種極端的情況:

    <div v-for="n in testArr"><transition name="toggle"><div class="info-block" v-if="isShow"></div></transition> </div>復制代碼 export default {data () {return {isShow: false,testArr: 1000}},methods: {toggle() {var self = this;setInterval(function () {self.isShow = !self.isShow}, 1000)}},mounted () {this.toggle()}}復制代碼 .toggle-show-enter {transform: translate(-400px,0);}.toggle-show-enter-active {color: white;}.toggle-show-enter-to {transform: translate(0,0);}.toggle-show-leave {transform: translate(0,0);}.toggle-show-leave-to {transform: translate(-400px,0);}.toggle-show-leave-active {color: white;}復制代碼

    上面的代碼在頁面中渲染了 1000 個過渡的元素,這些元素會在1秒的時間內從左側劃入,然后劃出。

    此時,我們打開google瀏覽器的開發(fā)者工具,然后在 performance 一欄中記錄分析性能,如下圖所示:

    可以發(fā)現,頁面明顯掉幀了。在7秒內,總共 scripting 的階段為3秒, rendering 階段為1956毫秒。

    事實上,這種跑馬燈式的重復式效果,通過 animation 的方式也可以輕松實現。 我們優(yōu)化上面的代碼,改為下面的代碼,通過 animation 動畫來控制過渡:

    <div v-for="n in testArr"><div class="info-block"></div></div> 復制代碼 export default {data () {return {isShow: false,testArr: 1000}}} 復制代碼.info-block {background-color: red;width: 300px;height: 100px;position: fixed;left: 10px;top: 200px;display: flex;align-items: center;justify-content: center;animation: toggleShow 3s ease 0s infinite normal; }@keyframes toggleShow {0% {transform: translate(-400px);}10% {transform: translate(0,0);}80% {transform: translate(0,0);}100% {transform: translate(-400px);} } 復制代碼

    打開瀏覽器的開發(fā)者工具,可以在 performance 里面看到,頁面性能有了驚人的提升:

    為了進一步提升頁面的性能,我們給過渡的元素增加一個 will-change 屬性,該元素就會被提升到 合成層 用GPU單獨渲染,這樣頁面性能就會有更大的提升。

    優(yōu)化懶加載(需考慮兼容性)

    有一些頁面使用了懶加載,懶加載是通過綁定 scroll 事件一個回調事件,每一次調用一次回調事件,就會測量一次元素的位置,調用 getBoundingClientRect() 方法,從而計算出是否元素出現在了可視區(qū)。

    // 懶加載庫中的代碼,判斷是否進入了可視區(qū) const isInView = (el, threshold) => {const {top, height} = el.getBoundingClientRect()return top < clientHeight + threshold && top + height > -threshold }復制代碼

    scroll 造成頁面性能下降

    scroll 事件會被重復的觸發(fā),每觸發(fā)一次就要測量一次元素的尺寸和位置。盡管對 scroll 的事件進行了節(jié)流的處理,但在低端安卓機上仍然會出現滑動不流暢的現象。

    優(yōu)化的思路是通過新增的api—— IntersectionObserver 來獲取元素是否進入了可視區(qū)。

    使用intersection observer

    intersection observer api 可以去測量某一個dom節(jié)點和其他節(jié)點,甚至是viewport的距離。

    這個是實驗性的api,你應該查閱https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#Browser_compatibility查看其兼容性

    在過去,檢測一個元素是否在可視區(qū)內,或者兩個元素之間的距離如何,是一個非常艱巨的任務。 但獲取這些信息是非常必要的:

  • 用于懶加載
  • 用于無限加載,就是微博那種刷到底接著請求新數據可以接著刷
  • 檢測廣告的可見性
  • 在過去,我們需要不斷的調用 Element.getBoundingClientRect() 方法去獲取到我們想拿到的信息,然而這些代碼會造成性能問題。

    intersection observer api 可以注冊回調函數,當我們的目標元素,進入指定區(qū)域(比如說viewport,或者其他的元素)時,回調函數會被觸發(fā);

    intersectionObserver 的語法

    var handleFun = function() {}var boxElement = document.getElementById()var options = {root: null,rootMargin: "0px",threshold: 0.01};observer = new IntersectionObserver(handleFunc, options);observer.observe(boxElement);復制代碼

    基于IntersectionObserver的懶加載的庫

    于是自己嘗試封裝了一個基于IntersectionObserver的懶加載的庫。

    html

    <img class="J_lazy-load" data-imgsrc="burger.png"> 復制代碼

    你也許注意到上面的代碼中,圖片文件沒有 src 屬性么。這是因為它使用了稱為 data-imgsrc 的 data 屬性來指向圖片源。我們將使用這來加載圖片

    js

    function lazyLoad(domArr) {if ('IntersectionObserver' in window) {let createObserver = (dom) => {var fn = (arr) => {let target = arr[0].targetif (arr[0].isIntersecting) {let imgsrc = target.dataset.imgsrcif (imgsrc) {target.setAttribute('src', imgsrc)}// 解除綁定觀察observer.unobserve(dom)}}var config = {root: null,rootMargin: '10px',threshold: 0.01}var observer = new IntersectionObserver(fn, config)observer.observe(dom)}Array.prototype.slice(domArr)domArr.forEach(dom => {createObserver(dom)})} }復制代碼

    這個庫的使用也非常簡單:

    // 先引入 import {lazyLoad} from '../util/lazyload.js'// 進行懶加載 let domArr = document.querySelectorAll('.J_lazy-load') lazyLoad(domArr)復制代碼

    然后測試一下,發(fā)現可以正常使用:

    比較性能

    傳統(tǒng)的懶加載 lazy-loder 的頁面性能如下:

    在12秒內,存在紅顏色的掉幀現象,一些地方的幀率偏低(在devtool里面是fps的綠色小山較高的地方),用于 scripting 階段的總共有600多ms.

    使用intersetctionObserver之后的懶加載性能如下:

    在12秒內,幀率比較平穩(wěn),用于 scripting 階段的時間只有60多ms了。

    參考鏈接:

  • hacks.mozilla.org/2017/08/ins…
  • codeburst.io/how-browser…
  • developer.mozilla.org/en-US/docs/…
  • www.chromium.org/developers/…
  • www.w3.org/TR/CSS22/vi…
  • css性能優(yōu)化
  • render tree
  • 總結

    以上是生活随笔為你收集整理的深入理解浏览器解析和执行过程的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    久久九九网站 | 成人av在线网址 | 99久久精品费精品 | 天天曰天天干 | 精品国偷自产国产一区 | 婷婷性综合 | 欧美一区二区在线刺激视频 | 麻豆传媒在线视频 | 天天爽夜夜爽人人爽曰av | 999久久久国产精品 高清av免费观看 | 免费观看视频黄 | 久久在线观看视频 | 综合色在线| 99久久www | 精品国产电影一区二区 | 超碰午夜| 日韩超碰在线 | 五月天久久综合网 | 久插视频 | 男女精品久久 | 免费观看一级视频 | 国产精品久久久视频 | 亚洲精选在线 | 免费看三级 | 黄色电影小说 | 日韩在线一区二区免费 | 久久超碰97 | 日韩欧美视频免费观看 | 国产精品字幕 | 欧美福利片在线观看 | 久草视频在线看 | 亚洲综合在线视频 | 一本一本久久a久久精品牛牛影视 | 日日夜夜网| 久久午夜电影院 | 91精品在线麻豆 | 欧美一级日韩三级 | 在线免费观看国产视频 | 婷婷精品国产欧美精品亚洲人人爽 | 深爱激情五月婷婷 | 中文字幕丝袜美腿 | 国产中文 | 91探花国产综合在线精品 | 六月激情婷婷 | 69视频网站 | 91麻豆精品国产午夜天堂 | 国产精品久久久久久久久久不蜜月 | 18国产精品福利片久久婷 | 久久久久久高潮国产精品视 | 日批视频在线播放 | 欧美二区在线播放 | 中文免费在线观看 | 四虎永久免费网站 | 久久视频国产精品免费视频在线 | 久久人人精品 | 国内精品久久久久久 | 久久只精品99品免费久23小说 | 久久久久国产精品免费网站 | 在线观看欧美成人 | 久久小视频 | 天天色天天色天天色 | 日韩网站在线观看 | 婷婷久久一区二区三区 | 日本黄色大片儿 | 婷婷在线综合 | 欧美xxxx性xxxxx高清 | 亚洲黄a| av免费网 | 亚洲撸撸 | 在线观看视频你懂的 | 视频二区 | 97碰在线 | 国产精品九色 | 久草在线电影网 | 九色91av| 日韩精品视频在线观看网址 | 97在线精品视频 | 日本最新高清不卡中文字幕 | 久久激情小视频 | 亚洲一区二区三区毛片 | 五月天六月婷婷 | 欧美亚洲免费在线一区 | 黄色亚洲| 婷婷国产视频 | 人人澡av | 涩av在线| 日日夜夜精品免费观看 | 久久久精品 | 中午字幕在线 | 91精品国产99久久久久久红楼 | 婷婷激情小说网 | 欧美精品一二三 | 国产精品免费不卡 | 国产日韩视频在线观看 | 99re国产 | 麻豆播放 | 四虎伊人 | 久久国产精品99久久人人澡 | 国产手机免费视频 | 国产视频1区2区 | 嫩嫩影院理论片 | 亚洲成a人片77777kkkk1在线观看 | 人人dvd| 中文字幕色婷婷在线视频 | 色婷婷www | 97视频在线观看成人 | 亚洲精品成人 | 欧美一二区视频 | 久久久亚洲国产精品麻豆综合天堂 | 手机在线视频福利 | 久久久久网站 | 欧美a级片免费看 | 精品美女视频 | 91精品国产高清自在线观看 | 国产五月婷婷 | 久久国产精品一二三区 | 日韩一区二区三区观看 | 久一在线 | 97香蕉久久国产在线观看 | 国产精品v欧美精品v日韩 | 综合网中文字幕 | 国产中文字幕三区 | 亚洲永久精品在线 | 91成人黄色 | 日韩高清免费在线 | www.狠狠操| 精品一区二区在线免费观看 | 成年人电影毛片 | 久久婷婷综合激情 | 在线观看黄色的网站 | 亚洲精品视频在线观看免费 | 六月丁香在线视频 | 欧美做受高潮1 | 欧美一区免费观看 | 国产视频一区二区在线 | 五月天中文字幕mv在线 | 国产精品久久久久永久免费观看 | 日韩欧美高清一区二区三区 | 亚洲在线不卡 | 1000部18岁以下禁看视频 | 精品国产亚洲一区二区麻豆 | 一区二区三区免费在线观看视频 | 久久精品91视频 | 精品国产精品久久一区免费式 | 亚洲久久视频 | 成人h动漫精品一区二 | 久久国产精品免费视频 | 91桃色国产在线播放 | av成人免费在线看 | 久久亚洲精品电影 | 麻豆系列在线观看 | 欧美性春潮 | 天天色天天艹 | 免费午夜视频在线观看 | 视频一区二区在线 | 国产又粗又长又硬免费视频 | h网站免费在线观看 | 久久激情综合 | 久精品视频| 2023亚洲精品国偷拍自产在线 | 亚洲午夜精品一区二区三区电影院 | 日韩av电影免费在线观看 | 精品国产日本 | 337p欧美 | 中文字幕欧美日韩va免费视频 | 九九免费在线视频 | 色婷婷在线观看视频 | 日本大尺码专区mv | 亚洲三级影院 | 四虎成人精品永久免费av | 欧美 另类 交 | 久久av中文字幕片 | 亚洲成人第一区 | 欧美一级片免费在线观看 | 91亚洲综合| 97精品国产| 国产又黄又爽无遮挡 | 综合色综合色 | 日本中文字幕高清 | av资源网在线播放 | 色姑娘综合网 | 色欲综合视频天天天 | 国产在线视频不卡 | 久久99精品久久久久久 | 五月婷婷丁香综合 | 亚洲欧洲久久久 | 十八岁以下禁止观看的1000个网站 | 一区二区三区高清在线观看 | 麻豆极品| 97电院网手机版 | 四季av综合网站 | 中文字幕在线电影 | 在线观看久 | 国产精品影音先锋 | 亚洲高清91 | 视频在线日韩 | 国产高清不卡av | 日韩毛片久久久 | 91麻豆视频 | 91理论片午午伦夜理片久久 | 在线视频你懂 | 国产香蕉av | 国产又粗又猛又黄又爽的视频 | 国产精品一区二区三区在线看 | 美女网站在线播放 | 国产精品日韩欧美 | 一区三区在线欧 | av线上免费看 | 国产午夜精品一区二区三区四区 | 91精品办公室少妇高潮对白 | 国内毛片毛片 | 国内综合精品午夜久久资源 | 精品九九九 | 一区二区三区高清在线观看 | 久久久九色精品国产一区二区三区 | 成人在线一区二区 | 亚洲经典视频在线观看 | 三级视频日韩 | 国产精品一区二 | 在线免费视频一区 | 亚洲国产一区在线观看 | 2022国产精品视频 | 99久久国产免费,99久久国产免费大片 | 国产精品福利小视频 | 国产精品午夜在线观看 | 在线免费看黄色 | 少妇高潮流白浆在线观看 | 九九亚洲视频 | 精品日韩在线一区 | 久久色亚洲 | 色婷婷av一区 | 在线高清一区 | 日本大尺码专区mv | 97视频在线观看免费 | 日韩在线网址 | 九九热在线观看视频 | 91久久奴性调教 | 日本在线观看一区二区 | 亚洲激情一区二区三区 | 久久午夜国产 | 91在线你懂的 | 国产一级免费观看视频 | 天天干天天上 | 五月激情久久久 | 黄色av免费| 欧美日韩在线视频观看 | 国产一级免费在线观看 | 99草在线视频 | 国产精品毛片一区二区 | 精品久久久久久久久中文字幕 | 午夜精品一二三区 | 亚洲国产精品一区二区久久hs | 狠狠干夜夜操 | 一级黄色网址 | 在线日本看片免费人成视久网 | 国产成人亚洲在线电影 | 国产第一页福利影院 | 亚洲乱码在线 | 91麻豆精品国产91久久久更新时间 | 久久99精品国产91久久来源 | 国产精品wwwwww | 欧美黑人巨大xxxxx | 亚洲成人网在线 | 久久久久久久网 | 欧美尹人 | 日本午夜在线亚洲.国产 | 国产伦精品一区二区三区… | 久精品在线观看 | 国产97超碰 | a在线免费 | 亚洲精品福利在线观看 | 91av在线视频播放 | 色综合久久精品 | 国产精品爽爽久久久久久蜜臀 | 国产专区欧美专区 | 国产午夜三级一区二区三桃花影视 | 国产3p视频| 日韩中文字幕亚洲一区二区va在线 | 黄色亚洲 | 国产精品一区二区你懂的 | 久久午夜免费视频 | 日韩免费观看av | 日韩动态视频 | 中文字幕色综合网 | 日韩欧美高清一区二区 | 一区二区三区久久精品 | 久久久免费少妇 | 美女免费黄网站 | 国产精品毛片一区视频 | 三级av网站| 一级一片免费观看 | 日韩美一区二区三区 | 日韩欧美精品免费 | 天天操天天射天天 | 国产精品一区二区久久国产 | 中文字幕av在线不卡 | 91插插插网站 | 色天天综合久久久久综合片 | www.福利视频 | 国产成人精品一区二区三区 | 国产不卡精品视频 | 天天色天 | 欧美影院久久 | 最新国产中文字幕 | 黄色精品免费 | 国产亚洲欧美日韩高清 | 亚洲日本韩国一区二区 | 欧美乱淫视频 | 丁香在线视频 | 久久久久久久久艹 | 波多野结衣日韩 | 成人国产精品久久久 | 91亚洲精品乱码久久久久久蜜桃 | 亚洲精品成人网 | 久久综合色一综合色88 | 国产一线二线三线性视频 | 国产精品亚洲精品 | 亚洲天天综合 | 中文字幕在线观看免费高清完整版 | 国产品久精国精产拍 | 黄av免费 | 一级免费看 | 国产破处精品 | 在线观看色视频 | 色婷婷久久| 成人av在线影院 | 国产精品免费小视频 | 亚州欧美视频 | 五月婷婷激情六月 | 在线视频中文字幕一区 | 国产成人一级 | 国产拍在线 | 激情五月婷婷综合网 | 高清av免费一区中文字幕 | 中文字幕在线资源 | 久久久综合九色合综国产精品 | 在线观看免费色 | 免费一级特黄录像 | 久久久久久国产精品美女 | 99精品在线视频播放 | 九草在线观看 | www一起操 | 又黄又爽免费视频 | 国产玖玖在线 | 九九亚洲视频 | 婷婷久久亚洲 | 久久伊人八月婷婷综合激情 | 日韩中文字幕亚洲一区二区va在线 | www色网站 | 欧美大片aaa | 国产最新91 | 日韩国产欧美在线播放 | 免费男女网站 | 久久精品国产亚洲 | 97精品视频在线播放 | 最近中文字幕在线 | 日韩av成人在线观看 | 久久婷综合 | 免费 在线 中文 日本 | 日韩专区在线播放 | 伊人中文字幕在线 | 国产色网 | 久久久久亚洲精品中文字幕 | 国产精品视频你懂的 | 午夜精品久久久久 | 91欧美日韩国产 | 成年人在线观看 | 婷婷色在线资源 | 国产精品视频大全 | 亚洲黄色大片 | 精品国产一区二区三区日日嗨 | 日日夜夜骑 | 日本精品一二区 | 日韩免费在线视频 | 久久一精品 | 亚洲精品视频网 | 久久精品成人欧美大片古装 | 国产在线一区二区三区播放 | 精品免费视频123区 午夜久久成人 | 色噜噜噜噜 | 免费精品人在线二线三线 | 国产不卡av在线 | 日韩电影在线观看一区二区三区 | 亚洲精品白浆高清久久久久久 | 日韩精品中文字幕在线观看 | 国产黑丝袜在线 | 日韩中文字幕在线不卡 | 国产麻豆传媒 | 国产麻豆剧传媒免费观看 | 色是在线视频 | 国产视频在线观看一区 | 亚洲精品影视在线观看 | 久久不射电影网 | 中文字幕乱码电影 | 欧美国产日韩在线视频 | 午夜 久久 tv| 91黄站| 午夜av电影院 | 欧美精品九九 | 久久99网 | 欧美伦理一区二区 | 精品uu| 91精品资源 | 国产一区在线免费观看 | 日本久久中文字幕 | 国产一级视屏 | 亚洲精品麻豆视频 | 性色av免费观看 | 日韩在线视频线视频免费网站 | 免费特级黄色片 | 久久久久久久久久免费视频 | 中文字幕av网站 | 国产一区视频导航 | 欧女人精69xxxxxx | www.com久久 | 国产手机视频在线观看 | 国产日韩欧美在线影视 | 黄污网| 国产91精品一区二区麻豆网站 | 可以免费看av | 国产精品第二十页 | 欧美日韩亚洲第一 | 久久久久黄 | 99久久99久久精品免费 | 日韩二区在线播放 | 久久精品一区二区三区国产主播 | 一区二区三区影院 | 婷婷在线播放 | 国模一二三区 | 人人干人人添 | 久久免费视频在线观看 | 一区二区三区四区在线免费观看 | 黄色一级大片在线免费看国产一 | 91精品免费看 | 国产xx视频 | 免费福利视频导航 | 日韩 在线观看 | 人人干,人人爽 | 久久国产精品免费 | 日韩av一区二区三区四区 | 久久久久久久久久久免费av | 99热在线精品观看 | 久久短视频 | 精品国产一区二区三区久久久久久 | 亚洲成人资源在线观看 | 免费日韩高清 | 久久dvd | 黄色精品一区 | 男女激情网址 | 91av官网 | 日日夜夜天天久久 | 日韩精品2区 | 日韩免费av网址 | 久久久不卡影院 | 91在线观看欧美日韩 | 99久久精品国产免费看不卡 | av手机在线播放 | 精品一区二区三区在线播放 | 成人久久久电影 | 久久字幕 | 成人a级黄色片 | 91麻豆精品国产自产在线游戏 | 视频三区在线 | 亚洲精品国产免费 | 中文资源在线播放 | 中文字幕人成不卡一区 | 伊人丁香| 欧美日韩国产伦理 | 成年人免费看av | 亚洲成人免费在线 | 国产精品久久综合 | 激情综合网五月 | 国产精品麻豆视频 | 久久不卡视频 | 国产不卡免费视频 | 免费涩涩网站 | 免费99| 日韩高清在线观看 | 国产成人1区 | 亚洲国产中文字幕在线观看 | 日韩综合精品 | 美女久久视频 | 免费视频97 | 中文字幕一区二区三区四区视频 | 久久精品久久综合 | 中文字幕在线视频一区二区三区 | 一区二区三区 亚洲 | 四虎国产精品成人免费影视 | 国产午夜精品免费一区二区三区视频 | 亚洲理论视频 | 中文字幕文字幕一区二区 | 99国产在线观看 | 日韩欧美精品在线 | 日本中文字幕电影在线免费观看 | 精品国产伦一区二区三区观看说明 | 国产综合婷婷 | av三区在线 | 国产精品一区二区三区四 | 亚洲精品白浆高清久久久久久 | 日韩免费看 | 丁香婷五月 | 香蕉视频久久久 | 最近中文字幕免费观看 | 九九九在线观看 | 久久久久亚洲精品 | 久久黄页 | 中文字幕人成乱码在线观看 | 久久综合综合久久综合 | 免费成人短视频 | 西西4444www大胆视频 | 婷婷色九月| 91av亚洲| 日本中文字幕网站 | 免费男女羞羞的视频网站中文字幕 | 日韩欧美一区二区三区在线 | 黄色小说视频网站 | 亚洲成人在线免费 | 国产精品综合久久久久 | 欧美在线观看视频一区二区三区 | 国内揄拍国产精品 | 开心激情婷婷 | 日本公妇在线观看高清 | 国产手机在线 | 激情五月综合网 | 日韩免费福利 | 日韩电影一区二区三区 | 国产精品麻豆三级一区视频 | 久久免费久久 | 99精品在线免费 | 成人夜晚看av | 91av播放 | 制服丝袜一区二区 | av不卡网站 | 天天草天天操 | 国产精品女主播一区二区三区 | 性色av免费在线观看 | 黄污在线观看 | 国产精品地址 | 在线91av| 国产拍揄自揄精品视频麻豆 | 国产123区在线观看 国产精品麻豆91 | 中文字幕在线观看完整版 | 久久久久久久影视 | 91麻豆精品久久久久久 | 欧美性成人 | 91传媒91久久久 | 精品久操 | 2019av在线视频| 日韩高清精品一区二区 | 91热视频 | 久草视频播放 | 国产日产精品久久久久快鸭 | 日本高清dvd | 最近中文字幕免费av | 中文字幕中文 | 精品免费观看视频 | www91在线观看 | 九九精品毛片 | 亚洲区精品视频 | 国内精品久久久久久 | 玖玖视频免费在线 | 国产精品成人一区二区三区 | 天天操狠狠操 | 国内99视频 | 国产网红在线 | 欧美日韩在线观看一区 | 国产成人无码AⅤ片在线观 日韩av不卡在线 | 国产精品亚洲视频 | 97**国产露脸精品国产 | 最近中文字幕完整视频高清1 | 日日夜夜精品 | 亚洲电影一区二区 | 亚洲精品欧美精品 | 99精品国产成人一区二区 | 亚洲妇女av| 国产精品乱码一区二三区 | 久久婷婷视频 | 亚洲精品视频在线播放 | 欧美在线视频一区二区 | 在线观看爱爱视频 | 久久精品视频在线免费观看 | 日韩网站视频 | 久久国产精品免费一区 | 看av免费网站| 日日干精品 | 欧美国产大片 | 91九色精品国产 | 超碰人人干人人 | 五月婷婷香蕉 | 欧美性黄网官网 | 亚洲三级在线 | 97国产情侣爱久久免费观看 | 日本三级全黄少妇三2023 | 日韩高清精品免费观看 | 免费午夜在线视频 | 久久久综合电影 | 日韩三级.com | 婷色在线 | 欧美精品中文 | 欧美激情综合五月色丁香 | 日韩综合一区二区 | 黄色小网站免费看 | 国产亚洲精品美女 | 国产亚洲精品久久久久动 | 91麻豆国产福利在线观看 | 色偷偷av男人天堂 | 天天操天天曰 | 午夜精品一区二区三区在线观看 | 日本h视频在线观看 | 91麻豆免费看 | 国产精品免费视频一区二区 | 久久国产精品电影 | 99热在线观看 | 色婷婷狠狠操 | 日韩av影片在线观看 | 亚洲精品影院在线观看 | 精品福利av | 视频在线观看入口黄最新永久免费国产 | 亚州欧美视频 | 国产精品久久在线观看 | 国产一区视频免费在线观看 | 国产精品99久久久久 | 国产乱码精品一区二区蜜臀 | 8x成人在线| 亚洲精品一区二区18漫画 | 精品国产一区二区三区久久久久久 | 欧美日韩中文另类 | 69视频在线播放 | 欧美一级特黄aaaaaa大片在线观看 | 黄色片免费电影 | 久久国产精品成人免费浪潮 | 久久成人精品 | 欧美日一级片 | 午夜国产福利在线 | 有码中文字幕 | 久草视频在线看 | 中文字幕一区二区在线播放 | 一区二区三区中文字幕在线 | 黄色特一级片 | 久久综合干 | 99视屏| 狠狠色免费 | 亚洲精品国产自产拍在线观看 | 一区在线播放 | 波多野结衣视频在线 | 亚洲精品在线视频播放 | 成年人视频在线免费观看 | 色欧美88888久久久久久影院 | 亚洲一区二区三区毛片 | 最新国产精品久久精品 | 丁香视频全集免费观看 | 国产香蕉在线 | 激情丁香久久 | 天干啦夜天干天干在线线 | 欧美日韩视频在线观看免费 | 国产福利免费看 | 国产精品第一视频 | a午夜电影 | 欧美久久久久 | 久久精彩 | 久久字幕 | 天天爱av导航 | 亚洲国产av精品毛片鲁大师 | 国产精品白浆视频 | 五月的婷婷 | 四虎国产精品永久在线国在线 | 欧美日韩后 | 国产亚洲欧洲 | 欧美另类z0zx| 国产不卡av在线播放 | 韩国av不卡 | 久久久久久久久久久福利 | 99久久精品免费看国产免费软件 | 人人插超碰 | 久草精品免费 | 91丨九色丨丝袜 | 最新精品视频在线 | 天天干,天天射,天天操,天天摸 | 黄网站免费久久 | 亚洲丝袜一区 | 日韩成人精品 | 日日操天天射 | 亚洲爱av| 国产日韩精品在线 | 91精品推荐| 免费的黄色的网站 | 97国产在线| 超碰人人乐 | 午夜999 | 久久a视频 | 国产精品18videosex性欧美 | 99久久久久国产精品免费 | 久久成人午夜视频 | 中文字幕有码在线观看 | 国产91在线免费视频 | 99久久精品国 | 波多野结衣在线播放视频 | 久久99精品久久久久久三级 | 久久国产精品影视 | 国产精品白浆 | 欧洲精品一区二区 | 97夜夜澡人人双人人人喊 | 久久精品国产精品亚洲 | 91麻豆免费版 | 五月天伊人 | 最新国产一区二区三区 | 最近日韩中文字幕中文 | 欧美日韩有码 | 黄色网中文字幕 | 99久久精品无免国产免费 | 97理论片 | 亚洲一二三久久 | 超碰97免费观看 | 亚洲在线a | 国产精品视频地址 | 亚洲天堂毛片 | 婷婷网五月天 | 国产精品乱码高清在线看 | 国产精品k频道 | 亚洲综合色丁香婷婷六月图片 | 在线免费观看视频一区 | 麻豆免费视频观看 | 国产在线久草 | 国产精品高清免费在线观看 | 成人av一二三区 | 久久精品亚洲 | 久久久99精品免费观看 | 99视频+国产日韩欧美 | 日本午夜在线亚洲.国产 | 最新中文字幕视频 | 免费福利小视频 | 超碰免费在线公开 | 午夜精品久久久99热福利 | 在线播放第一页 | 九九免费精品 | 国产专区欧美专区 | 亚洲一区二区三区在线看 | 亚洲精品久久激情国产片 | 久久精品99国产国产 | 国产99久久久精品 | 美女视频是黄的免费观看 | 国产精品成人一区 | 五月婷婷开心 | 91精品久久久久久久91蜜桃 | 亚洲人人爱| 色婷婷av在线 | 日日夜夜狠狠干 | 日本黄色黄网站 | 成人在线观看你懂的 | 在线亚洲高清视频 | 国产免费一区二区三区最新6 | 日韩免费av片 | 在线精品视频免费观看 | 成人免费网站在线观看 | 亚洲欧洲精品一区二区精品久久久 | 国产男女无遮挡猛进猛出在线观看 | 2019中文字幕第一页 | 91丨九色丨高潮丰满 | 91资源在线观看 | 国产色在线观看 | 中文字幕国产视频 | 婷婷丁香六月天 | 在线免费av观看 | 欧美日韩不卡一区二区 | 91av久久 | 天天干天天操 | 手机在线看永久av片免费 | 欧美日韩国产精品一区二区三区 | 日韩亚洲国产中文字幕 | 久草免费资源 | 99爱这里只有精品 | 日韩av免费大片 | 色爽网站 | 久久久私人影院 | 91爱爱网址| 天天骚夜夜操 | 免费a视频在线观看 | 在线观看中文字幕视频 | 久久av高清 | 国产精品九九九 | 国产剧情一区二区在线观看 | 久久精品久久精品久久 | 蜜臀av一区 | 2023天天干 | 黄色在线观看污 | 四虎成人精品永久免费av | 97夜夜澡人人爽人人免费 | 久久黄网站 | av免费电影在线观看 | 91自拍成人 | 天天干天天上 | 中文电影网 | 91av视频免费在线观看 | 成人免费共享视频 | 激情六月婷婷久久 | 日韩av影视 | 久久婷婷国产色一区二区三区 | 国模吧一区 | 五月天激情综合 | 国产精品婷婷午夜在线观看 | www五月天 | 久久精品视频在线免费观看 | 亚洲首页| 色操插| 毛片精品免费在线观看 | 中文字幕av专区 | 91pony九色丨交换 | 91在线视频 | 91精品国产91久久久久福利 | 美女视频黄免费网站 | 天天鲁天天干天天射 | 在线 你懂 | 日韩高清不卡在线 | 99精品免费观看 | 久久久久国产a免费观看rela | 国产999精品久久久影片官网 | 国产精品久久久久久久99 | 国产九九热视频 | 在线日韩中文 | 美女久久久久久久久久久 | 91精品视屏 | 日日夜夜爱| 午夜123| 免费亚洲一区二区 | 午夜精品一区二区三区四区 | 欧美在线1区 | 中文字幕丝袜制服 | 欧美一级淫片videoshd | 成人午夜黄色影院 | 五月天久久精品 | 深爱婷婷| 天天做天天看 | 青春草免费视频 | 国产精品美女毛片真酒店 | 在线观看一区二区精品 | 日韩精品免费在线视频 | 精品美女在线观看 | 激情www| 成年人免费在线观看 | 超碰在线日韩 | 日日爱网站 | 天天爱天天射天天干天天 | 亚洲日韩欧美视频 | 丰满少妇在线观看网站 | 亚洲视频精品在线 | 免费v片| 日韩色一区二区三区 | 在线日韩中文 | 天天操操操操操操 | 欧美日韩在线观看一区二区三区 | 91重口视频 | 蜜臀av性久久久久av蜜臀妖精 | 夜夜干夜夜 | 99久久精品免费看国产免费软件 | 狠狠做六月爱婷婷综合aⅴ 日本高清免费中文字幕 | 久久精品国产成人精品 | 免费看片日韩 | 国产一级电影免费观看 | 欧美一级性视频 | 日韩aⅴ视频 | 国产对白av | 日韩一二区在线观看 | 久久国产影视 | av一级久久| 国产一级性生活视频 | 91在线成人| av播放在线| 国产资源免费在线观看 | 国语黄色片 | 欧美精品在线观看免费 | 亚洲乱码久久久 | 午夜久久 | 欧美日韩在线精品一区二区 | av网站在线观看播放 | 天天躁天天躁天天躁婷 | 狠狠干美女 | 美女视频久久 | 日日夜精品 | www蜜桃视频 | 日韩精品中文字幕av | 国产99免费 | 91精选 | 免费观看成年人视频 | 天天色天天爱天天射综合 | 一级特黄aaa大片在线观看 | 99精品在线视频观看 | 国内精品亚洲 | 天天干,天天操,天天射 | 伊人色**天天综合婷婷 | 久久欧美精品 | 亚洲一区欧美精品 | 天天av天天| 亚洲精品免费在线播放 | 波多野结衣一区二区三区中文字幕 | 色婷婷综合激情 | 天堂视频中文在线 | 国产日韩av在线 | 亚洲一区二区三区四区精品 | 国产黄在线播放 | 欧美成年黄网站色视频 | 国产高清不卡在线 | sm免费xx网站 | 天天色天天射天天操 | 国产玖玖精品视频 | 日韩3区| 黄色av网站在线观看免费 | 久草在线资源观看 | 久久免费精品 | 丁香久久综合 | 色国产精品一区在线观看 | 国产成人一区在线 | 久久精品视频免费观看 | 精品视频一区在线 | 亚洲国产偷 | 国产一区av在线 | 婷婷精品 | 天天插天天狠天天透 | 特级毛片爽www免费版 | 精品视频亚洲 | 天天操天天插 | 午夜视频在线观看一区二区三区 | 国产高清在线看 | 9999国产| 天海冀一区二区三区 | 色婷婷成人 | 中文在线免费视频 | 99热在| 国产视频精品久久 | 久久麻豆视频 | 91麻豆精品国产91久久久久 | 伊人天天| 欧美日韩久久不卡 | 2020天天干夜夜爽 | 日韩av电影中文字幕 | 日韩精品专区 | 欧美激情操 | 91av亚洲 | 99免费在线视频观看 | 国产直播av| 成片人卡1卡2卡3手机免费看 | 九九久久精品 | 欧洲在线免费视频 | av在观看 | 国产综合视频在线观看 | av天天色 | 九色琪琪久久综合网天天 | 正在播放国产精品 | 国产成人精品av久久 | 国产自偷自拍 | 美女视频久久久 | 视频一区视频二区在线观看 | 狠狠狠色丁香综合久久天下网 | 国产精品一区二区美女视频免费看 | 国产伦精品一区二区三区无广告 | 精品日韩中文字幕 | 成人av在线电影 | 91传媒91久久久 | 福利在线看片 | 狠狠干狠狠艹 | 夜夜躁日日躁狠狠久久88av | 日韩精品在线观看av | 久久另类小说 | 国产精品一区二区三区在线播放 | 96视频在线| 成在线播放 | 黄色小说免费观看 | 亚洲成人中文在线 | 日韩v在线91成人自拍 | 国产一性一爱一乱一交 | 99视频免费观看 | 一级片免费观看视频 | 在线免费黄色 | 亚洲国产精彩中文乱码av | 最近2019好看的中文字幕免费 | 美女网站黄在线观看 | 国产黄色特级片 | 国产真实精品久久二三区 | 精品久久久久久久久久久久 | 欧美成人tv | 国产精品99久久久久久武松影视 | 蜜桃麻豆www久久囤产精品 | 亚洲国产操 | 国产精品激情 | 九九精品久久 | 国产日韩在线播放 | 国产一级高清 | 久久久亚洲麻豆日韩精品一区三区 | 成人午夜黄色影院 | 久久精品美女视频 | 色操插| 亚洲精品福利在线观看 | 在线播放亚洲 | 国产亚洲人成网站在线观看 | 成人在线电影观看 | 欧美精品久久久久久久久久白贞 | 国产91在线观看 | 亚洲综合一区二区精品导航 | 欧美激情精品一区 |