摘要:前言見解有限,如有描述不當之處,請幫忙指出,如有錯誤,會及時修正。為什么要梳理這篇文章最近恰好被問到這方面的問題,嘗試整理后發現,這道題的覆蓋面可以非常廣,很適合作為一道承載知識體系的題目。
前言
見解有限,如有描述不當之處,請幫忙指出,如有錯誤,會及時修正。
為什么要梳理這篇文章?
最近恰好被問到這方面的問題,嘗試整理后發現,這道題的覆蓋面可以非常廣,很適合作為一道承載知識體系的題目。
關于這道題目的吐槽暫且不提(這是一道被提到無數次的題,得到不少人的贊同,也被很多人反感),本文的目的是如何借助這道題梳理自己的前端知識體系!
竊認為,每一個前端人員,如果要往更高階發展,必然會將自己的知識體系梳理一遍,沒有牢固的知識體系,無法往更高處走!
展現形式:本文并不是將所有的知識點列一遍,而是偏向于分析+梳理
內容:在本文中只會梳理一些比較重要的前端向知識點,其它的可能會被省略
目標:本文的目標是梳理一個較為完整的前端向知識體系
本文是個人階段性梳理知識體系的成果,然后加以修繕后發布成文章,因此并不確保適用于所有人員,但是,個人認為本文還是有一定參考價值的
另外,如有不同見解,可以一起討論
----------超長文預警,需要花費大量時間。----------
本文適合有一定經驗的前端人員,新手請規避。
本文內容超多,建議先了解主干,然后分成多批次閱讀。
本文是前端向,以前端領域的知識為重點
大綱對知識體系進行一次預評級
為什么說知識體系如此重要?
梳理主干流程
從瀏覽器接收url到開啟網絡請求線程
多進程的瀏覽器
多線程的瀏覽器內核
解析URL
網絡請求都是多帶帶的線程
更多
開啟網絡線程到發出一個完整的http請求
DNS查詢得到IP
tcp/ip請求
五層因特網協議棧
從服務器接收到請求到對應后臺接收到請求
負載均衡
后臺的處理
后臺和前臺的http交互
http報文結構
cookie以及優化
gzip壓縮
長連接與短連接
http 2.0
https
多帶帶拎出來的緩存問題,http的緩存
強緩存與弱緩存
緩存頭部簡述
頭部的區別
解析頁面流程
流程簡述
HTML解析,構建DOM
生成CSS規則
構建渲染樹
渲染
簡單層與復合層
Chrome中的調試
資源外鏈的下載
loaded和domcontentloaded
CSS的可視化格式模型
包含塊(Containing Block)
控制框(Controlling Box)
BFC(Block Formatting Context)
IFC(Inline Formatting Context)
其它
JS引擎解析過程
JS的解釋階段
JS的預處理階段
JS的執行階段
回收機制
其它
總結
對知識體系進行一次預評級看到這道題目,不借助搜索引擎,自己的心里是否有一個答案?
這里,以目前的經驗(了解過一些處于不同階段的相關前端人員的情況),大概有以下幾種情況:(以下都是以點見面,實際上不同階段人員一般都會有其它的隱藏知識點的)
level1:完全沒什么概念的,支支吾吾的回答,一般就是這種水平(大致形象點描述):
瀏覽器發起請求,服務端返回數據,然后前端解析成網頁,執行腳本。。。
這類人員一般都是:
萌新(剛接觸前端的,包括0-6個月都有可能有這種回答)
沉淀人員(就是那種可能已經接觸了前端幾年,但是仍然處于初級階段的那種。。。)
當然了,后者一般還會偶爾提下http、后臺、瀏覽器渲染,js引擎等等關鍵字,但基本都是一詳細的問就不知道了。。。
level2:已經有初步概念,但是可能沒有完整梳理過,導致無法形成一個完整的體系,或者是很多細節都不會展開,大概是這樣子的:(可能符合若干條)
知道瀏覽器輸入url后會有http請求這個概念
有后臺這個概念,大致知道前后端的交互,知道前后端只要靠http報文通信
知道瀏覽器接收到數據后會進行解析,有一定概念,但是具體流程不熟悉(如render樹構建流程,layout、paint,復合層與簡單層,常用優化方案等不是很熟悉)
對于js引擎的解析流程有一定概念,但是細節不熟悉(如具體的形參,函數,變量提升,執行上下文以及VO、AO、作用域鏈,回收機制等概念不是很熟悉)
如可能知道一些http規范初步概念,但是不熟悉(如http報文結構,常用頭部,緩存機制,http2.0,https等特性,跨域與web安全等不是很熟悉)
到這里,看到這上面一大堆的概念后,心里應該也會有點底了。。。
實際上,大部分的前端人員可能都處于level2,但是,跳出這個階段并不容易,一般需要積累,不斷學習,才能水到渠成
這類人員一般都是:
工作1-3年左右的普通人員(占大多數,而且大多數人員工作3年左右并沒有實質上的提升)
工作3年以上的老人(這部分人大多都業務十分嫻熟,一個當好幾個用,但是,基礎比較薄弱,可能沒有嘗試寫過框架、組件、腳手架等)
大部分的初中級都陷在這個階段,如果要突破,不斷學習,積累,自然能水到渠成,打通任督二脈
level3:基本能到這一步的,不是高階就是接近高階,因為很多概念并不是靠背就能理解的,而要理解這么多,需形成體系,一般都需要積累,非一日之功。
一般包括什么樣的回答呢?(這里就以自己的簡略回答進行舉例),一般這個階段的人員都會符合若干條(不一定全部,當然可能還有些是這里遺漏的):
首先略去那些鍵盤輸入、和操作系統交互、以及屏幕顯示原理、網卡等硬件交互之類的(前端向中,很多硬件原理暫時略去。。。)
對瀏覽器模型有整體概念,知道瀏覽器是多進程的,瀏覽器內核是多線程的,清楚進程與線程之間得區別,以及輸入url后會開一個新的網絡線程
對從開啟網絡線程到發出一個完整的http請求中間的過程有所了解(如dns查詢,tcp/ip鏈接,五層因特網協議棧等等,以及一些優化方案,如dns-prefetch)
對從服務器接收到請求到對應后臺接收到請求有一定了解(如負載均衡,安全攔截以及后臺代碼處理等)
對后臺和前臺的http交互熟悉(包括http報文結構,場景頭部,cookie,跨域,web安全,http緩存,http2.0,https等)
對瀏覽器接收到http數據包后的解析流程熟悉(包括解析html,詞法分析然后解析成dom樹、解析css生成css規則樹、合并成render樹,然后layout、painting渲染、里面可能還包括復合圖層的合成、GPU繪制、外鏈處理、加載順序等)
對JS引擎解析過程熟悉(包括JS的解釋,預處理,執行上下文,VO,作用域鏈,this,回收機制等)
可以看到,上述包括了一大堆的概念,僅僅是偏前端向,而且沒有詳細展開,就已經如此之多的概念了,所以,個人認為如果沒有自己的見解,沒有形成自己的知識體系,僅僅是看看,背背是沒用的,過一段時間就會忘光了。
再說下一般這個階段的都可能是什么樣的人吧。(不一定準確,這里主要是靠少部分現實以及大部分推測得出)
工作2年以上的前端(基本上如果按正常進度的話,至少接觸前端兩年左右才會開始走向高階,當然,現在很多都是上學時就開始學了的,還有部分是天賦異稟,不好預估。。。)
或者是已經十分熟悉其它某門語言,再轉前端的人(基本上是很快就可以將前端水準提升上去)
一般符合這個條件的都會有各種隱藏屬性(如看過各大框架、組件的源碼,寫過自己的組件、框架、腳手架,做過大型項目,整理過若干精品博文等)
level4:由于本人層次尚未達到,所以大致說下自己的見解吧。
一般這個層次,很多大佬都并不僅僅是某個技術棧了,而是成為了技術專家,技術leader之類的角色。所以僅僅是回答某個技術問題已經無法看出水準了,
可能更多的要看架構,整體把控,大型工程構建能力等等
不過,對于某些執著于技術的大佬,大概會有一些回答吧:(猜的)
從鍵盤談起到系統交互,從瀏覽器到CPU,從調度機制到系統內核,從數據請求到二進制、匯編,從GPU繪圖到LCD顯示,然后再分析系統底層的進程、內存等等
總之,從軟件到硬件,到材料,到分子,原子,量子,薛定諤的貓,人類起源,宇宙大爆炸,平行宇宙?感覺都毫無違和感。。。
這點可以參考下本題的原始出處:
http://fex.baidu.com/blog/2014/05/what-happen/
為什么說知識體系如此重要?為什么說知識體系如此重要呢?這里舉幾個例子
假設有被問到這樣一道題目(隨意想到的一個):
如何理解getComputedStyle
在尚未梳理知識體系前,大概會這樣回答:
普通版本:getComputedStyle會獲取當前元素所有最終使用的CSS屬性值(最終計算后的結果),通過window.getComputedStyle等價于document.defaultView.getComputedStyle調用
詳細版本:window.getComputedStyle(elem, null).getPropertyValue("height")可能的值為100px,而且,就算是css上寫的是inherit,getComputedStyle也會把它最終計算出來的。不過注意,如果元素的背景色透明,那么getComputedStyle獲取出來的就是透明的這個背景(因為透明本身也是有效的),而不會是父節點的背景。所以它不一定是最終顯示的顏色。
就這個API來說,上述的回答已經比較全面了。
但是,其實它是可以繼續延伸的。
譬如現在會這樣回答:
getComputedStyle會獲取當前元素所有最終使用的CSS屬性值,window.和document.defaultView.等價...
getComputedStyle會引起回流,因為它需要獲取祖先節點的一些信息進行計算(譬如寬高等),所以用的時候慎用,回流會引起性能問題。然后合適的話會將話題引導回流,重繪,瀏覽器渲染原理等等。當然也可以列舉一些其它會引發回流的操作,如offsetXXX,scrollXXX,clientXXX,currentStyle等等
再舉一個例子:
visibility: hidden和display: none的區別
可以如下回答:
普通回答,一個隱藏,但占據位置,一個隱藏,不占據位置
進一步,display由于隱藏后不占據位置,所以造成了dom樹的改變,會引發回流,代價較大
再進一步,當一個頁面某個元素經常需要切換display時如何優化,一般會用復合層優化,或者要求低一點用absolute讓其脫離普通文檔流也行。然后可以將話題引到普通文檔流,absolute文檔流,復合圖層的區別,
再進一步可以描述下瀏覽器渲染原理以及復合圖層和普通圖層的繪制區別(復合圖層多帶帶分配資源,獨立繪制,性能提升,但是不能過多,還有隱式合成等等)
上面這些大概就是知識系統化后的回答,會更全面,容易由淺入深,而且一有機會就可以往更底層挖
前端向知識的重點此部分的內容是站在個人視角分析的,并不是說就一定是正確答案
首先明確,計算機方面的知識是可以無窮無盡的挖的,而本文的重點是梳理前端向的重點知識
對于前端向(這里可能沒有提到node.js之類的,更多的是指客戶端前端),這里將知識點按重要程度劃分成以下幾大類:
核心知識,必須掌握的,也是最基礎的,譬如瀏覽器模型,渲染原理,JS解析過程,JS運行機制等,作為骨架來承載知識體系
重點知識,往往每一塊都是一個知識點,而且這些知識點都很重要,譬如http相關,web安全相關,跨域處理等
拓展知識,這一塊可能更多的是了解,稍微實踐過,但是認識上可能沒有上面那么深刻,譬如五層因特網協議棧,hybrid模式,移動原生開發,后臺相關等等(當然,在不同領域,可能有某些知識就上升到重點知識層次了,譬如hybrid開發時,懂原生開發是很重要的)
為什么要按上面這種方式劃分?
這大概與個人的技術成長有關。
記得最開始學前端知識時,是一點一點的積累,一個知識點一個知識點的攻克。
就這樣,雖然在很長一段時間內積累了不少的知識,但是,總是無法將它串聯到一起。每次梳理時都是很分散的,無法保持思路連貫性。
直到后來,在將瀏覽器渲染原理、JS運行機制、JS引擎解析流程梳理一遍后,感覺就跟打通了任督二脈一樣,有了一個整體的架構,以前的知識點都連貫起來了。
梳理出了一個知識體系,以后就算再學新的知識,也會盡量往這個體系上靠攏,環環相扣,更容易理解,也更不容易遺忘
梳理主干流程回到這道題上,如何回答呢?先梳理一個骨架
知識體系中,最重要的是骨架,脈絡。有了骨架后,才方便填充細節。所以,先梳理下主干流程:
1. 從瀏覽器接收url到開啟網絡請求線程(這一部分可以展開瀏覽器的機制以及進程與線程之間的關系) 2. 開啟網絡線程到發出一個完整的http請求(這一部分涉及到dns查詢,tcp/ip請求,五層因特網協議棧等知識) 3. 從服務器接收到請求到對應后臺接收到請求(這一部分可能涉及到負載均衡,安全攔截以及后臺內部的處理等等) 4. 后臺和前臺的http交互(這一部分包括http頭部、響應碼、報文結構、cookie等知識,可以提下靜態資源的cookie優化,以及編碼解碼,如gzip壓縮等) 5. 多帶帶拎出來的緩存問題,http的緩存(這部分包括http緩存頭部,etag,catch-control等) 6. 瀏覽器接收到http數據包后的解析流程(解析html-詞法分析然后解析成dom樹、解析css生成css規則樹、合并成render樹,然后layout、painting渲染、復合圖層的合成、GPU繪制、外鏈資源的處理、loaded和domcontentloaded等) 7. CSS的可視化格式模型(元素的渲染規則,如包含塊,控制框,BFC,IFC等概念) 8. JS引擎解析過程(JS的解釋階段,預處理階段,執行階段生成執行上下文,VO,作用域鏈、回收機制等等) 9. 其它(可以拓展不同的知識模塊,如跨域,web安全,hybrid模式等等內容)
梳理出主干骨架,然后就需要往骨架上填充細節內容
從瀏覽器接收url到開啟網絡請求線程這一部分展開的內容是:瀏覽器進程/線程模型,JS的運行機制
多進程的瀏覽器瀏覽器是多進程的,有一個主控進程,以及每一個tab頁面都會新開一個進程(某些情況下多個tab會合并進程)
進程可能包括主控進程,插件進程,GPU,tab頁(瀏覽器內核)等等
Browser進程:瀏覽器的主進程(負責協調、主控),只有一個
第三方插件進程:每種類型的插件對應一個進程,僅當使用該插件時才創建
GPU進程:最多一個,用于3D繪制
瀏覽器渲染進程(內核):默認每個Tab頁面一個進程,互不影響,控制頁面渲染,腳本執行,事件處理等(有時候會優化,如多個空白tab會合并成一個進程)
如下圖:
多線程的瀏覽器內核每一個tab頁面可以看作是瀏覽器內核進程,然后這個進程是多線程的,它有幾大類子線程
GUI線程
JS引擎線程
事件觸發線程
定時器線程
網絡請求線程
可以看到,里面的JS引擎是內核進程中的一個線程,這也是為什么常說JS引擎是單線程的
解析URL輸入URL后,會進行解析(URL的本質就是統一資源定位符)
URL一般包括幾大部分:
protocol,協議頭,譬如有http,ftp等
host,主機域名或IP地址
port,端口號
path,目錄路徑
query,即查詢參數
fragment,即#后的hash值,一般用來定位到某個位置
網絡請求都是多帶帶的線程每次網絡請求時都需要開辟多帶帶的線程進行,譬如如果URL解析到http協議,就會新建一個網絡線程去處理資源下載
因此瀏覽器會根據解析出得協議,開辟一個網絡線程,前往請求資源(這里,暫時理解為是瀏覽器內核開辟的,如有錯誤,后續修復)
更多由于篇幅關系,這里就大概介紹一個主干流程,關于瀏覽器的進程機制,更多可以參考以前總結的一篇文章(因為內容實在過多,里面包括JS運行機制,進程線程的詳解)
從瀏覽器多進程到JS單線程,JS運行機制最全面的一次梳理
開啟網絡線程到發出一個完整的http請求這一部分主要內容包括:dns查詢,tcp/ip請求構建,五層因特網協議棧等等
仍然是先梳理主干,有些詳細的過程不展開(因為展開的話內容過多)
DNS查詢得到IP如果輸入的是域名,需要進行dns解析成IP,大致流程:
如果瀏覽器有緩存,直接使用瀏覽器緩存,否則使用本機緩存,再沒有的話就是用host
如果本地沒有,就向dns域名服務器查詢(當然,中間可能還會經過路由,也有緩存等),查詢到對應的IP
注意,域名查詢時有可能是經過了CDN調度器的(如果有cdn存儲功能的話)
而且,需要知道dns解析是很耗時的,因此如果解析域名過多,會讓首屏加載變得過慢,可以考慮dns-prefetch優化
這一塊可以深入展開,具體請去網上搜索,這里就不占篇幅了(網上可以看到很詳細的解答)
tcp/ip請求http的本質就是tcp/ip請求
需要了解3次握手規則建立連接以及斷開連接時的四次揮手
tcp將http長報文劃分為短報文,通過三次握手與服務端建立連接,進行可靠傳輸
三次握手的步驟:(抽象派)
客戶端:hello,你是server么? 服務端:hello,我是server,你是client么 客戶端:yes,我是client
建立連接成功后,接下來就正式傳輸數據
然后,待到斷開連接時,需要進行四次揮手(因為是全雙工的,所以需要四次揮手)
四次揮手的步驟:(抽象派)
主動方:我已經關閉了向你那邊的主動通道了,只能被動接收了 被動方:收到通道關閉的信息 被動方:那我也告訴你,我這邊向你的主動通道也關閉了 主動方:最后收到數據,之后雙方無法通信
tcp/ip的并發限制
瀏覽器對同一域名下并發的tcp連接是有限制的(2-10個不等)
而且在http1.0中往往一個資源下載就需要對應一個tcp/ip請求
所以針對這個瓶頸,又出現了很多的資源優化方案
get和post的區別
get和post雖然本質都是tcp/ip,但兩者除了在http層面外,在tcp/ip層面也有區別。
get會產生一個tcp數據包,post兩個
具體就是:
get請求時,瀏覽器會把headers和data一起發送出去,服務器響應200(返回數據),
post請求時,瀏覽器先發送headers,服務器響應100 continue,
瀏覽器再發送data,服務器響應200(返回數據)。
再說一點,這里的區別是specification(規范)層面,而不是implementation(對規范的實現)
五層因特網協議棧其實這個概念挺難記全的,記不全沒關系,但是要有一個整體概念
其實就是一個概念: 從客戶端發出http請求到服務器接收,中間會經過一系列的流程。
簡括就是:
從應用層的發送http請求,到傳輸層通過三次握手建立tcp/ip連接,再到網絡層的ip尋址,再到數據鏈路層的封裝成幀,最后到物理層的利用物理介質傳輸。
當然,服務端的接收就是反過來的步驟
五層因特網協議棧其實就是:
1.應用層(dns,http) DNS解析成IP并發送http請求 2.傳輸層(tcp,udp) 建立tcp連接(三次握手) 3.網絡層(IP,ARP) IP尋址 4.數據鏈路層(PPP) 封裝成幀 5.物理層(利用物理介質傳輸比特流) 物理傳輸(然后傳輸的時候通過雙絞線,電磁波等各種介質)
當然,其實也有一個完整的OSI七層框架,與之相比,多了會話層、表示層。
OSI七層框架:物理層、數據鏈路層、網絡層、傳輸層、會話層、表示層、應用層
表示層:主要處理兩個通信系統中交換信息的表示方式,包括數據格式交換,數據加密與解密,數據壓縮與終端類型轉換等 會話層:它具體管理不同用戶和進程之間的對話,如控制登陸和注銷過程從服務器接收到請求到對應后臺接收到請求
服務端在接收到請求時,內部會進行很多的處理
這里由于不是專業的后端分析,所以只是簡單的介紹下,不深入
負載均衡對于大型的項目,由于并發訪問量很大,所以往往一臺服務器是吃不消的,所以一般會有若干臺服務器組成一個集群,然后配合反向代理實現負載均衡
當然了,負載均衡不止這一種實現方式,這里不深入...
簡單的說:
用戶發起的請求都指向調度服務器(反向代理服務器,譬如安裝了nginx控制負載均衡),然后調度服務器根據實際的調度算法,分配不同的請求給對應集群中的服務器執行,然后調度器等待實際服務器的HTTP響應,并將它反饋給用戶
后臺的處理一般后臺都是部署到容器中的,所以一般為:
先是容器接受到請求(如tomcat容器)
然后對應容器中的后臺程序接收到請求(如java程序)
然后就是后臺會有自己的統一處理,處理完后響應響應結果
概括下:
一般有的后端是有統一的驗證的,如安全攔截,跨域驗證
如果這一步不符合規則,就直接返回了相應的http報文(如拒絕請求等)
然后當驗證通過后,才會進入實際的后臺代碼,此時是程序接收到請求,然后執行(譬如查詢數據庫,大量計算等等)
等程序執行完畢后,就會返回一個http響應包(一般這一步也會經過多層封裝)
然后就是將這個包從后端發送到前端,完成交互
后臺和前臺的http交互前后端交互時,http報文作為信息的載體
所以http是一塊很重要的內容,這一部分重點介紹它
http報文結構報文一般包括了:通用頭部,請求/響應頭部,請求/響應體
通用頭部
這也是開發人員見過的最多的信息,包括如下:
Request Url: 請求的web服務器地址 Request Method: 請求方式 (Get、POST、OPTIONS、PUT、HEAD、DELETE、CONNECT、TRACE) Status Code: 請求的返回狀態碼,如200代表成功 Remote Address: 請求的遠程服務器地址(會轉為IP)
譬如,在跨域拒絕時,可能是method為options,狀態碼為404/405等(當然,實際上可能的組合有很多)
其中,Method的話一般分為兩批次:
HTTP1.0定義了三種請求方法: GET, POST 和 HEAD方法。 以及幾種Additional Request Methods:PUT、DELETE、LINK、UNLINK HTTP1.1定義了八種請求方法:GET、POST、HEAD、OPTIONS, PUT, DELETE, TRACE 和 CONNECT 方法。
HTTP 1.0定義參考:https://tools.ietf.org/html/rfc1945
HTTP 1.1定義參考:https://tools.ietf.org/html/rfc2616
這里面最常用到的就是狀態碼,很多時候都是通過狀態碼來判斷,如(列舉幾個最常見的):
200——表明該請求被成功地完成,所請求的資源發送回客戶端 304——自從上次請求后,請求的網頁未修改過,請客戶端使用本地緩存 400——客戶端請求有錯(譬如可以是安全模塊攔截) 401——請求未經授權 403——禁止訪問(譬如可以是未登錄時禁止) 404——資源未找到 500——服務器內部錯誤 503——服務不可用 ...
再列舉下大致不同范圍狀態的意義
1xx——指示信息,表示請求已接收,繼續處理 2xx——成功,表示請求已被成功接收、理解、接受 3xx——重定向,要完成請求必須進行更進一步的操作 4xx——客戶端錯誤,請求有語法錯誤或請求無法實現 5xx——服務器端錯誤,服務器未能實現合法的請求
總之,當請求出錯時,狀態碼能幫助快速定位問題,完整版本的狀態可以自行去互聯網搜索
請求/響應頭部
請求和響應頭部也是分析時常用到的
常用的請求頭部(部分):
Accept: 接收類型,表示瀏覽器支持的MIME類型 (對標服務端返回的Content-Type) Accept-Encoding:瀏覽器支持的壓縮類型,如gzip等,超出類型不能接收 Content-Type:客戶端發送出去實體內容的類型 Cache-Control: 指定請求和響應遵循的緩存機制,如no-cache If-Modified-Since:對應服務端的Last-Modified,用來匹配看文件是否變動,只能精確到1s之內,http1.0中 Expires:緩存控制,在這個時間內不會請求,直接使用緩存,http1.0,而且是服務端時間 Max-age:代表資源在本地緩存多少秒,有效時間內不會請求,而是使用緩存,http1.1中 If-None-Match:對應服務端的ETag,用來匹配文件內容是否改變(非常精確),http1.1中 Cookie: 有cookie并且同域訪問時會自動帶上 Connection: 當瀏覽器與服務器通信時對于長連接如何進行處理,如keep-alive Host:請求的服務器URL Origin:最初的請求是從哪里發起的(只會精確到端口),Origin比Referer更尊重隱私 Referer:該頁面的來源URL(適用于所有類型的請求,會精確到詳細頁面地址,csrf攔截常用到這個字段) User-Agent:用戶客戶端的一些必要信息,如UA頭部等
常用的響應頭部(部分):
Access-Control-Allow-Headers: 服務器端允許的請求Headers Access-Control-Allow-Methods: 服務器端允許的請求方法 Access-Control-Allow-Origin: 服務器端允許的請求Origin頭部(譬如為*) Content-Type:服務端返回的實體內容的類型 Date:數據從服務器發送的時間 Cache-Control:告訴瀏覽器或其他客戶,什么環境可以安全的緩存文檔 Last-Modified:請求資源的最后修改時間 Expires:應該在什么時候認為文檔已經過期,從而不再緩存它 Max-age:客戶端的本地資源應該緩存多少秒,開啟了Cache-Control后有效 ETag:請求變量的實體標簽的當前值 Set-Cookie:設置和頁面關聯的cookie,服務器通過這個頭部把cookie傳給客戶端 Keep-Alive:如果客戶端有keep-alive,服務端也會有響應(如timeout=38) Server:服務器的一些相關信息
一般來說,請求頭部和響應頭部是匹配分析的。
譬如,請求頭部的Accept要和響應頭部的Content-Type匹配,否則會報錯
譬如,跨域請求時,請求頭部的Origin要匹配響應頭部的Access-Control-Allow-Origin,否則會報跨域錯誤
譬如,在使用緩存時,請求頭部的If-Modified-Since、If-None-Match分別和響應頭部的Last-Modified、ETag對應
還有很多的分析方法,這里不一一贅述
請求/響應實體
http請求時,除了頭部,還有消息實體,一般來說
請求實體中會將一些需要的參數都放入進入(用于post請求)。
譬如實體中可以放參數的序列化形式(a=1&b=2這種),或者直接放表單對象(Form Data對象,上傳時可以夾雜參數以及文件),等等
而一般響應實體中,就是放服務端需要傳給客戶端的內容
一般現在的接口請求時,實體中就是對于的信息的json格式,而像頁面請求這種,里面就是直接放了一個html字符串,然后瀏覽器自己解析并渲染。
CRLF
CRLF(Carriage-Return Line-Feed),意思是回車換行,一般作為分隔符存在
請求頭和實體消息之間有一個CRLF分隔,響應頭部和響應實體之間用一個CRLF分隔
一般來說(分隔符類別):
CRLF->Windows-style LF->Unix Style CR->Mac Style
如下圖是對某請求的http報文結構的簡要分析
cookie以及優化cookie是瀏覽器的一種本地存儲方式,一般用來幫助客戶端和服務端通信的,常用來進行身份校驗,結合服務端的session使用。
場景如下(簡述):
在登陸頁面,用戶登陸了 此時,服務端會生成一個session,session中有對于用戶的信息(如用戶名、密碼等) 然后會有一個sessionid(相當于是服務端的這個session對應的key) 然后服務端在登錄頁面中寫入cookie,值就是:jsessionid=xxx 然后瀏覽器本地就有這個cookie了,以后訪問同域名下的頁面時,自動帶上cookie,自動檢驗,在有效時間內無需二次登陸。
上述就是cookie的常用場景簡述(當然了,實際情況下得考慮更多因素)
一般來說,cookie是不允許存放敏感信息的(千萬不要明文存儲用戶名、密碼),因為非常不安全,如果一定要強行存儲,首先,一定要在cookie中設置httponly(這樣就無法通過js操作了),另外可以考慮rsa等非對稱加密(因為實際上,瀏覽器本地也是容易被攻克的,并不安全)
另外,由于在同域名的資源請求時,瀏覽器會默認帶上本地的cookie,針對這種情況,在某些場景下是需要優化的。
譬如以下場景:
客戶端在域名A下有cookie(這個可以是登陸時由服務端寫入的) 然后在域名A下有一個頁面,頁面中有很多依賴的靜態資源(都是域名A的,譬如有20個靜態資源) 此時就有一個問題,頁面加載,請求這些靜態資源時,瀏覽器會默認帶上cookie 也就是說,這20個靜態資源的http請求,每一個都得帶上cookie,而實際上靜態資源并不需要cookie驗證 此時就造成了較為嚴重的浪費,而且也降低了訪問速度(因為內容更多了)
當然了,針對這種場景,是有優化方案的(多域名拆分)。具體做法就是:
將靜態資源分組,分別放到不同的域名下(如static.base.com)
而page.base.com(頁面所在域名)下請求時,是不會帶上static.base.com域名的cookie的,所以就避免了浪費
說到了多域名拆分,這里再提一個問題,那就是:
在移動端,如果請求的域名數過多,會降低請求速度(因為域名整套解析流程是很耗費時間的,而且移動端一般帶寬都比不上pc)
此時就需要用到一種優化方案:dns-prefetch(讓瀏覽器空閑時提前解析dns域名,不過也請合理使用,勿濫用)
關于cookie的交互,可以看下圖總結
gzip壓縮首先,明確gzip是一種壓縮格式,需要瀏覽器支持才有效(不過一般現在瀏覽器都支持),
而且gzip壓縮效率很好(高達70%左右)
然后gzip一般是由apache、tomcat等web服務器開啟
當然服務器除了gzip外,也還會有其它壓縮格式(如deflate,沒有gzip高效,且不流行)
所以一般只需要在服務器上開啟了gzip壓縮,然后之后的請求就都是基于gzip壓縮格式的,
非常方便。
首先看tcp/ip層面的定義:
長連接:一個tcp/ip連接上可以連續發送多個數據包,在tcp連接保持期間,如果沒有數據包發送,需要雙方發檢測包以維持此連接,一般需要自己做在線維持(類似于心跳包)
短連接:通信雙方有數據交互時,就建立一個tcp連接,數據發送完成后,則斷開此tcp連接
然后在http層面:
http1.0中,默認使用的是短連接,也就是說,瀏覽器沒進行一次http操作,就建立一次連接,任務結束就中斷連接,譬如每一個靜態資源請求時都是一個多帶帶的連接
http1.1起,默認使用長連接,使用長連接會有這一行Connection: keep-alive,在長連接的情況下,當一個網頁打開完成后,客戶端和服務端之間用于傳輸http的tcp連接不會關閉,如果客戶端再次訪問這個服務器的頁面,會繼續使用這一條已經建立的連接
注意: keep-alive不會永遠保持,它有一個持續時間,一般在服務器中配置(如apache),另外長連接需要客戶端和服務器都支持時才有效
http 2.0http2.0不是https,它相當于是http的下一代規范(譬如https的請求可以是http2.0規范的)
然后簡述下http2.0與http1.1的顯著不同點:
http1.1中,每請求一個資源,都是需要開啟一個tcp/ip連接的,所以對應的結果是,每一個資源對應一個tcp/ip請求,由于tcp/ip本身有并發數限制,所以當資源一多,速度就顯著慢下來
http2.0中,一個tcp/ip請求可以請求多個資源,也就是說,只要一次tcp/ip請求,就可以請求若干個資源,分割成更小的幀請求,速度明顯提升。
所以,如果http2.0全面應用,很多http1.1中的優化方案就無需用到了(譬如打包成精靈圖,靜態資源多域名拆分等)
然后簡述下http2.0的一些特性:
多路復用(即一個tcp/ip連接可以請求多個資源)
首部壓縮(http頭部壓縮,減少體積)
二進制分幀(在應用層跟傳送層之間增加了一個二進制分幀層,改進傳輸性能,實現低延遲和高吞吐量)
服務器端推送(服務端可以對客戶端的一個請求發出多個響應,可以主動通知客戶端)
請求優先級(如果流被賦予了優先級,它就會基于這個優先級來處理,由服務器決定需要多少資源來處理該請求。)
httpshttps就是安全版本的http,譬如一些支付等操作基本都是基于https的,因為http請求的安全系數太低了。
簡單來看,https與http的區別就是: 在請求前,會建立ssl鏈接,確保接下來的通信都是加密的,無法被輕易截取分析
一般來說,如果要將網站升級成https,需要后端支持(后端需要申請證書等),然后https的開銷也比http要大(因為需要額外建立安全鏈接以及加密等),所以一般來說http2.0配合https的體驗更佳(因為http2.0更快了)
一般來說,主要關注的就是SSL/TLS的握手流程,如下(簡述):
1. 瀏覽器請求建立SSL鏈接,并向服務端發送一個隨機數–Client random和客戶端支持的加密方法,比如RSA加密,此時是明文傳輸。 2. 服務端從中選出一組加密算法與Hash算法,回復一個隨機數–Server random,并將自己的身份信息以證書的形式發回給瀏覽器 (證書里包含了網站地址,非對稱加密的公鑰,以及證書頒發機構等信息) 3. 瀏覽器收到服務端的證書后 - 驗證證書的合法性(頒發機構是否合法,證書中包含的網址是否和正在訪問的一樣),如果證書信任,則瀏覽器會顯示一個小鎖頭,否則會有提示 - 用戶接收證書后(不管信不信任),瀏覽會生產新的隨機數–Premaster secret,然后證書中的公鑰以及指定的加密方法加密`Premaster secret`,發送給服務器。 - 利用Client random、Server random和Premaster secret通過一定的算法生成HTTP鏈接數據傳輸的對稱加密key-`session key` - 使用約定好的HASH算法計算握手消息,并使用生成的`session key`對消息進行加密,最后將之前生成的所有信息發送給服務端。 4. 服務端收到瀏覽器的回復 - 利用已知的加解密方式與自己的私鑰進行解密,獲取`Premaster secret` - 和瀏覽器相同規則生成`session key` - 使用`session key`解密瀏覽器發來的握手消息,并驗證Hash是否與瀏覽器發來的一致 - 使用`session key`加密一段握手消息,發送給瀏覽器 5. 瀏覽器解密并計算握手消息的HASH,如果與服務端發來的HASH一致,此時握手過程結束,
之后所有的https通信數據將由之前瀏覽器生成的session key并利用對稱加密算法進行加密
這里放一張圖(來源:阮一峰-圖解SSL/TLS協議)
多帶帶拎出來的緩存問題,http的緩存前后端的http交互中,使用緩存能很大程度上的提升效率,而且基本上對性能有要求的前端項目都是必用緩存的
強緩存與弱緩存緩存可以簡單的劃分成兩種類型:強緩存(200 from cache)與協商緩存(304)
區別簡述如下:
強緩存(200 from cache)時,瀏覽器如果判斷本地緩存未過期,就直接使用,無需發起http請求
協商緩存(304)時,瀏覽器會向服務端發起http請求,然后服務端告訴瀏覽器文件未改變,讓瀏覽器使用本地緩存
對于協商緩存,使用Ctrl + F5強制刷新可以使得緩存無效
但是對于強緩存,在未過期時,必須更新資源路徑才能發起新的請求(更改了路徑相當于是另一個資源了,這也是前端工程化中常用到的技巧)
緩存頭部簡述上述提到了強緩存和協商緩存,那它們是怎么區分的呢?
答案是通過不同的http頭部控制
先看下這幾個頭部:
If-None-Match/E-tag、If-Modified-Since/Last-Modified、Cache-Control/Max-Age、Pragma/Expires
這些就是緩存中常用到的頭部,這里不展開。僅列舉下大致使用。
屬于強緩存控制的:
(http1.1)Cache-Control/Max-Age (http1.0)Pragma/Expires
注意:Max-Age不是一個頭部,它是Cache-Control頭部的值
屬于協商緩存控制的:
(http1.1)If-None-Match/E-tag (http1.0)If-Modified-Since/Last-Modified
可以看到,上述有提到http1.1和http1.0,這些不同的頭部是屬于不同http時期的
再提一點,其實HTML頁面中也有一個meta標簽可以控制緩存方案-Pragma
不過,這種方案還是比較少用到,因為支持情況不佳,譬如緩存代理服務器肯定不支持,所以不推薦
頭部的區別首先明確,http的發展是從http1.0到http1.1
而在http1.1中,出了一些新內容,彌補了http1.0的不足。
http1.0中的緩存控制:
Pragma:嚴格來說,它不屬于專門的緩存控制頭部,但是它設置no-cache時可以讓本地強緩存失效(屬于編譯控制,來實現特定的指令,主要是因為兼容http1.0,所以以前又被大量應用)
Expires:服務端配置的,屬于強緩存,用來控制在規定的時間之前,瀏覽器不會發出請求,而是直接使用本地緩存,注意,Expires一般對應服務器端時間,如Expires:Fri, 30 Oct 1998 14:19:41
If-Modified-Since/Last-Modified:這兩個是成對出現的,屬于協商緩存的內容,其中瀏覽器的頭部是If-Modified-Since,而服務端的是Last-Modified,它的作用是,在發起請求時,如果If-Modified-Since和Last-Modified匹配,那么代表服務器資源并未改變,因此服務端不會返回資源實體,而是只返回頭部,通知瀏覽器可以使用本地緩存。Last-Modified,顧名思義,指的是文件最后的修改時間,而且只能精確到1s以內
http1.1中的緩存控制:
Cache-Control:緩存控制頭部,有no-cache、max-age等多種取值
Max-Age:服務端配置的,用來控制強緩存,在規定的時間之內,瀏覽器無需發出請求,直接使用本地緩存,注意,Max-Age是Cache-Control頭部的值,不是獨立的頭部,譬如Cache-Control: max-age=3600,而且它值得是絕對時間,由瀏覽器自己計算
If-None-Match/E-tag:這兩個是成對出現的,屬于協商緩存的內容,其中瀏覽器的頭部是If-None-Match,而服務端的是E-tag,同樣,發出請求后,如果If-None-Match和E-tag匹配,則代表內容未變,通知瀏覽器使用本地緩存,和Last-Modified不同,E-tag更精確,它是類似于指紋一樣的東西,基于FileEtag INode Mtime Size生成,也就是說,只要文件變,指紋就會變,而且沒有1s精確度的限制。
Max-Age相比Expires?
Expires使用的是服務器端的時間
但是有時候會有這樣一種情況-客戶端時間和服務端不同步
那這樣,可能就會出問題了,造成了瀏覽器本地的緩存無用或者一直無法過期
所以一般http1.1后不推薦使用Expires
而Max-Age使用的是客戶端本地時間的計算,因此不會有這個問題
因此推薦使用Max-Age。
注意,如果同時啟用了Cache-Control與Expires,Cache-Control優先級高。
E-tag相比Last-Modified?
Last-Modified:
表明服務端的文件最后何時改變的
它有一個缺陷就是只能精確到1s,
然后還有一個問題就是有的服務端的文件會周期性的改變,導致緩存失效
而E-tag:
是一種指紋機制,代表文件相關指紋
只有文件變才會變,也只要文件變就會變,
也沒有精確時間的限制,只要文件一遍,立馬E-tag就不一樣了
如果同時帶有E-tag和Last-Modified,服務端會優先檢查E-tag
各大緩存頭部的整體關系如下圖
解析頁面流程前面有提到http交互,那么接下來就是瀏覽器獲取到html,然后解析,渲染
這部分很多都參考了網上資源,特別是圖片,參考了來源中的文章
流程簡述瀏覽器內核拿到內容后,渲染步驟大致可以分為以下幾步:
1. 解析HTML,構建DOM樹 2. 解析CSS,生成CSS規則樹 3. 合并DOM樹和CSS規則,生成render樹 4. 布局render樹(Layout/reflow),負責各元素尺寸、位置的計算 5. 繪制render樹(paint),繪制頁面像素信息 6. 瀏覽器會將各層的信息發送給GPU,GPU會將各層合成(composite),顯示在屏幕上
如下圖:
HTML解析,構建DOM整個渲染步驟中,HTML解析是第一步。
簡單的理解,這一步的流程是這樣的:瀏覽器解析HTML,構建DOM樹。
但實際上,在分析整體構建時,卻不能一筆帶過,得稍微展開。
解析HTML到構建出DOM當然過程可以簡述如下:
Bytes → characters → tokens → nodes → DOM
譬如假設有這樣一個HTML頁面:(以下部分的內容出自參考來源,修改了下格式)
Critical Path Hello web performance students!
瀏覽器的處理如下:
列舉其中的一些重點過程:
1. Conversion轉換:瀏覽器將獲得的HTML內容(Bytes)基于他的編碼轉換為單個字符 2. Tokenizing分詞:瀏覽器按照HTML規范標準將這些字符轉換為不同的標記token。每個token都有自己獨特的含義以及規則集 3. Lexing詞法分析:分詞的結果是得到一堆的token,此時把他們轉換為對象,這些對象分別定義他們的屬性和規則 4. DOM構建:因為HTML標記定義的就是不同標簽之間的關系,這個關系就像是一個樹形結構一樣 例如:body對象的父節點就是HTML對象,然后段略p對象的父節點就是body對象
最后的DOM樹如下:
生成CSS規則同理,CSS規則樹的生成也是類似。簡述為:
Bytes → characters → tokens → nodes → CSSOM
譬如style.css內容如下:
body { font-size: 16px } p { font-weight: bold } span { color: red } p span { display: none } img { float: right }
那么最終的CSSOM樹就是:
構建渲染樹當DOM樹和CSSOM都有了后,就要開始構建渲染樹了
一般來說,渲染樹和DOM樹相對應的,但不是嚴格意義上的一一對應
因為有一些不可見的DOM元素不會插入到渲染樹中,如head這種不可見的標簽或者display: none等
整體來說可以看圖:
渲染有了render樹,接下來就是開始渲染,基本流程如下:
圖中重要的四個步驟就是:
1. 計算CSS樣式 2. 構建渲染樹 3. 布局,主要定位坐標和大小,是否換行,各種position overflow z-index屬性 4. 繪制,將圖像繪制出來
然后,圖中的線與箭頭代表通過js動態修改了DOM或CSS,導致了重新布局(Layout)或渲染(Repaint)
這里Layout和Repaint的概念是有區別的:
Layout,也稱為Reflow,即回流。一般意味著元素的內容、結構、位置或尺寸發生了變化,需要重新計算樣式和渲染樹
Repaint,即重繪。意味著元素發生的改變只是影響了元素的一些外觀之類的時候(例如,背景色,邊框顏色,文字顏色等),此時只需要應用新樣式繪制這個元素就可以了
回流的成本開銷要高于重繪,而且一個節點的回流往往回導致子節點以及同級節點的回流,
所以優化方案中一般都包括,盡量避免回流。
什么會引起回流?
1.頁面渲染初始化 2.DOM結構改變,比如刪除了某個節點 3.render樹變化,比如減少了padding 4.窗口resize 5.最復雜的一種:獲取某些屬性,引發回流, 很多瀏覽器會對回流做優化,會等到數量足夠時做一次批處理回流, 但是除了render樹的直接變化,當獲取一些屬性時,瀏覽器為了獲得正確的值也會觸發回流,這樣使得瀏覽器優化無效,包括 (1)offset(Top/Left/Width/Height) (2) scroll(Top/Left/Width/Height) (3) cilent(Top/Left/Width/Height) (4) width,height (5) 調用了getComputedStyle()或者IE的currentStyle
回流一定伴隨著重繪,重繪卻可以多帶帶出現
所以一般會有一些優化方案,如:
減少逐項更改樣式,最好一次性更改style,或者將樣式定義為class并一次性更新
避免循環操作dom,創建一個documentFragment或div,在它上面應用所有DOM操作,最后再把它添加到window.document
避免多次讀取offset等屬性。無法避免則將它們緩存到變量
將復雜的元素絕對定位或固定定位,使得它脫離文檔流,否則回流代價會很高
注意:改變字體大小會引發回流
再來看一個示例:
var s = document.body.style; s.padding = "2px"; // 回流+重繪 s.border = "1px solid red"; // 再一次 回流+重繪 s.color = "blue"; // 再一次重繪 s.backgroundColor = "#ccc"; // 再一次 重繪 s.fontSize = "14px"; // 再一次 回流+重繪 // 添加node,再一次 回流+重繪 document.body.appendChild(document.createTextNode("abc!"));簡單層與復合層
上述中的渲染中止步于繪制,但實際上繪制這一步也沒有這么簡單,它可以結合復合層和簡單層的概念來講。
這里不展開,進簡單介紹下:
可以認為默認只有一個復合圖層,所有的DOM節點都是在這個復合圖層下的
如果開啟了硬件加速功能,可以將某個節點變成復合圖層
復合圖層之間的繪制互不干擾,由GPU直接控制
而簡單圖層中,就算是absolute等布局,變化時不影響整體的回流,但是由于在同一個圖層中,仍然是會影響繪制的,因此做動畫時性能仍然很低。而復合層是獨立的,所以一般做動畫推薦使用硬件加速
更多參考:
普通圖層和復合圖層
Chrome中的調試Chrome的開發者工具中,Performance中可以看到詳細的渲染過程:
上面介紹了html解析,渲染流程。但實際上,在解析html時,會遇到一些資源連接,此時就需要進行多帶帶處理了
簡單起見,這里將遇到的靜態資源分為一下幾大類(未列舉所有):
CSS樣式資源
JS腳本資源
img圖片類資源
遇到外鏈時的處理
當遇到上述的外鏈時,會多帶帶開啟一個下載線程去下載資源(http1.1中是每一個資源的下載都要開啟一個http請求,對應一個tcp/ip鏈接)
遇到CSS樣式資源
CSS資源的處理有幾個特點:
CSS下載時異步,不會阻塞瀏覽器構建DOM樹
但是會阻塞渲染,也就是在構建render時,會等到css下載解析完畢后才進行(這點與瀏覽器優化有關,防止css規則不斷改變,避免了重復的構建)
有例外,media query聲明的CSS是不會阻塞渲染的
遇到JS腳本資源
JS腳本資源的處理有幾個特點:
阻塞瀏覽器的解析,也就是說發現一個外鏈腳本時,需等待腳本下載完成并執行后才會繼續解析HTML
瀏覽器的優化,一般現代瀏覽器有優化,在腳本阻塞時,也會繼續下載其它資源(當然有并發上限),但是雖然腳本可以并行下載,解析過程仍然是阻塞的,也就是說必須這個腳本執行完畢后才會接下來的解析,并行下載只是一種優化而已
defer與async,普通的腳本是會阻塞瀏覽器解析的,但是可以加上defer或async屬性,這樣腳本就變成異步了,可以等到解析完畢后再執行
注意,defer和async是有區別的: defer是延遲執行,而async是異步執行。
簡單的說(不展開):
async是異步執行,異步下載完畢后就會執行,不確保執行順序,一定在onload前,但不確定在DOMContentLoaded事件的前或后
defer是延遲執行,在瀏覽器看起來的效果像是將腳本放在了body后面一樣(雖然按規范應該是在DOMContentLoaded事件前,但實際上不同瀏覽器的優化效果不一樣,也有可能在它后面)
遇到img圖片類資源
遇到圖片等資源時,直接就是異步下載,不會阻塞解析,下載完畢后直接用圖片替換原有src的地方
loaded和domcontentloaded簡單的對比:
DOMContentLoaded 事件觸發時,僅當DOM加載完成,不包括樣式表,圖片(譬如如果有async加載的腳本就不一定完成)
load 事件觸發時,頁面上所有的DOM,樣式表,腳本,圖片都已經加載完成了
CSS的可視化格式模型這一部分內容很多參考《精通CSS-高級Web標準解決方案》以及參考來源
前面提到了整體的渲染概念,但實際上文檔樹中的元素是按什么渲染規則渲染的,是可以進一步展開的,此部分內容即: CSS的可視化格式模型
先了解:
CSS中規定每一個元素都有自己的盒子模型(相當于規定了這個元素如何顯示)
然后可視化格式模型則是把這些盒子按照規則擺放到頁面上,也就是如何布局
換句話說,盒子模型規定了怎么在頁面里擺放盒子,盒子的相互作用等等
說到底: CSS的可視化格式模型就是規定了瀏覽器在頁面中如何處理文檔樹
關鍵字:
包含塊(Containing Block) 控制框(Controlling Box) BFC(Block Formatting Context) IFC(Inline Formatting Context) 定位體系 浮動 ...
另外,CSS有三種定位機制:普通流,浮動,絕對定位,如無特別提及,下文中都是針對普通流中的
包含塊(Containing Block)一個元素的box的定位和尺寸,會與某一矩形框有關,這個框就稱之為包含塊。
元素會為它的子孫元素創建包含塊,但是,并不是說元素的包含塊就是它的父元素,元素的包含塊與它的祖先元素的樣式等有關系
譬如:
根元素是最頂端的元素,它沒有父節點,它的包含塊就是初始包含塊
static和relative的包含塊由它最近的塊級、單元格或者行內塊祖先元素的內容框(content)創建
fixed的包含塊是當前可視窗口
absolute的包含塊由它最近的position 屬性為absolute、relative或者fixed的祖先元素創建
如果其祖先元素是行內元素,則包含塊取決于其祖先元素的direction特性
如果祖先元素不是行內元素,那么包含塊的區域應該是祖先元素的內邊距邊界
控制框(Controlling Box)塊級元素和塊框以及行內元素和行框的相關概念
塊框:
塊級元素會生成一個塊框(Block Box),塊框會占據一整行,用來包含子box和生成的內容
塊框同時也是一個塊包含框(Containing Box),里面要么只包含塊框,要么只包含行內框(不能混雜),如果塊框內部有塊級元素也有行內元素,那么行內元素會被匿名塊框包圍
關于匿名塊框的生成,示例:
Some textMore text
div生成了一個塊框,包含了另一個塊框p以及文本內容Some text,此時Some text文本會被強制加到一個匿名的塊框里面,被div生成的塊框包含(其實這個就是IFC中提到的行框,包含這些行內框的這一行匿名塊形成的框,行框和行內框不同)
換句話說:
如果一個塊框在其中包含另外一個塊框,那么我們強迫它只能包含塊框,因此其它文本內容生成出來的都是匿名塊框(而不是匿名行內框)
行內框:
一個行內元素生成一個行內框
行內元素能排在一行,允許左右有其它元素
關于匿名行內框的生成,示例:
Some emphasized text
P元素生成一個塊框,其中有幾個行內框(如EM),以及文本Some , text,此時會專門為這些文本生成匿名行內框
display屬性的影響
display的幾個屬性也可以影響不同框的生成:
block,元素生成一個塊框
inline,元素產生一個或多個的行內框
inline-block,元素產生一個行內級塊框,行內塊框的內部會被當作塊塊來格式化,而此元素本身會被當作行內級框來格式化(這也是為什么會產生BFC)
none,不生成框,不再格式化結構中,當然了,另一個visibility: hidden則會產生一個不可見的框
總結:
如果一個框里,有一個塊級元素,那么這個框里的內容都會被當作塊框來進行格式化,因為只要出現了塊級元素,就會將里面的內容分塊幾塊,每一塊獨占一行(出現行內可以用匿名塊框解決)
如果一個框里,沒有任何塊級元素,那么這個框里的內容會被當成行內框來格式化,因為里面的內容是按照順序成行的排列
BFC(Block Formatting Context)FC(格式上下文)?
FC即格式上下文,它定義框內部的元素渲染規則,比較抽象,譬如
FC像是一個大箱子,里面裝有很多元素 箱子可以隔開里面的元素和外面的元素(所以外部并不會影響FC內部的渲染) 內部的規則可以是:如何定位,寬高計算,margin折疊等等
不同類型的框參與的FC類型不同,譬如塊級框對應BFC,行內框對應IFC
注意,并不是說所有的框都會產生FC,而是符合特定條件才會產生,只有產生了對應的FC后才會應用對應渲染規則
BFC規則:
在塊格式化上下文中 每一個元素左外邊與包含塊的左邊相接觸(對于從右到左的格式化,右外邊接觸右邊) 即使存在浮動也是如此(所以浮動元素正常會直接貼近它的包含塊的左邊,與普通元素重合) 除非這個元素也創建了一個新的BFC
總結幾點BFC特點:
內部box在垂直方向,一個接一個的放置
box的垂直方向由margin決定,屬于同一個BFC的兩個box間的margin會重疊
BFC區域不會與float box重疊(可用于排版)
BFC就是頁面上的一個隔離的獨立容器,容器里面的子元素不會影響到外面的元素。反之也如此
計算BFC的高度時,浮動元素也參與計算(不會浮動坍塌)
如何觸發BFC?
根元素
float屬性不為none
position為absolute或fixed
display為inline-block, flex, inline-flex,table,table-cell,table-caption
overflow不為visible
這里提下,display: table,它本身不產生BFC,但是它會產生匿名框(包含display: table-cell的框),而這個匿名框產生BFC
更多請自行網上搜索
IFC(Inline Formatting Context)IFC即行內框產生的格式上下文
IFC規則
在行內格式化上下文中 框一個接一個地水平排列,起點是包含塊的頂部。 水平方向上的 margin,border 和 padding 在框之間得到保留 框在垂直方向上可以以不同的方式對齊:它們的頂部或底部對齊,或根據其中文字的基線對齊
行框
包含那些框的長方形區域,會形成一行,叫做行框
行框的寬度由它的包含塊和其中的浮動元素決定,高度的確定由行高度計算規則決定<
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/93349.html
摘要:前言一直混跡社區突然發現自己收藏了不少好文但是管理起來有點混亂所以將前端主流技術做了一個書簽整理不求最多最全但求最實用。 前言 一直混跡社區,突然發現自己收藏了不少好文但是管理起來有點混亂; 所以將前端主流技術做了一個書簽整理,不求最多最全,但求最實用。 書簽源碼 書簽導入瀏覽器效果截圖showImg(https://segmentfault.com/img/bVbg41b?w=107...
摘要:接下來深入的介紹幾種性能優化的小最小化重繪和重排既然重排重繪是會影響頁面的性能,尤其是糟糕的代碼更會將重排帶來的性能問題放大。前面有講到,當訪問諸如,這種屬性時,會沖破瀏覽器自有的優化通過隊列化修改和批量運行的方法,減少重排重繪版次。 前言,最近利用碎片時間拜讀了一下尼古拉斯的另一巨作《高性能JavaScript》,今天寫的文章從老生常談的頁面重繪和重排入手,去探究這兩個概念在頁面性能...
摘要:通過管理組件通信通過驅動視圖比較差異進行更新操作作者第七頁鏈接來源知乎著作權歸作者所有,轉載請聯系作者獲得授權。達到無刷新的效果。對象的狀態不受外界影響。對象代表一個異步操作,有三種狀態進行中已完成,又稱和已失敗。 以下問題解釋非本人原創,是根據面試經驗整理后覺得更容易理解的解釋版本,歡迎補充。 一. 輸入url后的加載過程 從輸入 URL 到頁面加載完成的過程中都發生了什么 計算機...
摘要:通過管理組件通信通過驅動視圖比較差異進行更新操作作者第七頁鏈接來源知乎著作權歸作者所有,轉載請聯系作者獲得授權。達到無刷新的效果。對象的狀態不受外界影響。對象代表一個異步操作,有三種狀態進行中已完成,又稱和已失敗。 以下問題解釋非本人原創,是根據面試經驗整理后覺得更容易理解的解釋版本,歡迎補充。 一. 輸入url后的加載過程 從輸入 URL 到頁面加載完成的過程中都發生了什么 計算機...
閱讀 2676·2023-04-25 20:19
閱讀 1930·2021-11-24 09:38
閱讀 1631·2021-11-16 11:44
閱讀 4341·2021-09-02 15:40
閱讀 1316·2019-08-30 15:55
閱讀 2021·2019-08-30 15:52
閱讀 3759·2019-08-29 17:20
閱讀 2245·2019-08-29 13:48