天是 2020 年 4 月 4 日,星期六,清明節。
我們的國家經歷了非常慘痛的時刻,很多英雄在救助他人的路上倒下,更有很多烈士英雄保衛人民的安危遇難,今天全國下降半旗,北京時間 10 點全國默哀三分鐘,來致敬英雄們。同時今天一切公共娛樂活動也都會停止,包括直播、綜藝、影視、游戲等等。
我在這里也向全國抗擊新冠肺炎疫情斗爭犧牲的烈士和逝世的同胞表達深切的哀悼,向所有抗戰在疫情前線的工作和醫護人員致敬。我們每一個人的平安面前,都是英雄的人墻。
今天大家可以看到很多很多網站包括主頁和內容也都已經變成了灰色,比如百度、B 站、愛奇藝、CSDN 等等。
CSDN
愛奇藝
百度
大家可以看到全站的內容都變成灰色了,包括按鈕、圖片等等。這時候我們可能會好奇這是怎么做到的呢?
有人會以為所有的內容都統一換了一個 CSS 樣式,圖片也全換成灰色的了,按鈕等樣式也統一換成了灰色樣式。但你想想這個成本也太高了,而且萬一某個控件忘記加灰色樣式了豈不是太突兀了。
其實,解決方案很簡單,只需要幾行代碼就能搞定了。
我們選擇一個網站,比如 B 站吧,打開瀏覽器開發者工具。
審查一下網頁的源代碼,我們可以發現在 html 的這個地方多了一個疑似的 class,叫做 gray,gray 中文即灰色。
變灰效果
其 CSS 內容為:
html.gray {
-webkit-filter: grayscale(.95);
}
我們將其取消,就能發現網站的顏色就能重新還原回來了。
還原效果
果然是這個樣式在起作用,而且是全局的效果,因為它是作用在了 html 這個節點之上的。
另外看看 CSDN,它也是用的這個 CSS 樣式,其內容為:
html {
-webkit-filter: grayscale(100%);
-moz-filter: grayscale(100%);
-ms-filter: grayscale(100%);
-o-filter: grayscale(100%);
filter: grayscale(100%);
filter: progid:DXImageTransform.Microsoft.BasicImage(grayscale=1);
}
這個實現看起來兼容性會更好一些。
因此我們可以確定,通過一個全局的 CSS 樣式就能將整個網站變成灰色效果。
那么這里我們就來詳細了解一下這究竟是一個什么樣的 CSS 樣式。
這個樣式名叫做 filter,搜下 MDN 的官方介紹,其鏈接為:https://developer.mozilla.org/zh-CN/docs/Web/CSS/filter。
官方介紹內容如下:
filter CSS 屬性將模糊或顏色偏移等圖形效果應用于元素。濾鏡通常用于調整圖像,背景和邊框的渲染。
CSS 標準里包含了一些已實現預定義效果的函數。你也可以參考一個 SVG 濾鏡,通過一個 URL 鏈接到 SVG 濾鏡元素 (SVG filter element[1])。
其實就是一個濾鏡的意思。
官方有一個 Demo,可以看下效果,如圖所示。
Demo
比如這里通過 filter 樣式改變了圖片、顏色、模糊、對比度等等信息。
其所有用法示例如下:
/* URL to SVG filter */
filter: url("filters.svg#filter-id");
/* <filter-function> values */
filter: blur(5px);
filter: brightness(0.4);
filter: contrast(200%);
filter: drop-shadow(16px 16px 20px blue);
filter: grayscale(50%);
filter: hue-rotate(90deg);
filter: invert(75%);
filter: opacity(25%);
filter: saturate(30%);
filter: sepia(60%);
/* Multiple filters */
filter: contrast(175%) brightness(3%);
/* Global values */
filter: inherit;
filter: initial;
filter: unset;
各個用法介紹大家可以參考官方的文檔說明:https://developer.mozilla.org/zh-CN/docs/Web/CSS/filter
比如這里如果我們可以使用 blur 設置高斯模糊,用法如下:
filter: blur(radius)
給圖像設置高斯模糊。radius 一值設定高斯函數的標準差,或者是屏幕上以多少像素融在一起,所以值越大越模糊;如果沒有設定值,則默認是 0;這個參數可設置絕對像素值,但不接受百分比值。
可以達成這樣的效果:
效果
再說回剛才的灰色圖像,這里其實就是設置了 grayscale,其用法如下:
filter: grayscale(percent)
將圖像轉換為灰度圖像。值定義轉換的比例。percent 值為 100% 則完全轉為灰度圖像,值為 0% 圖像無變化。值在 0% 到 100% 之間,則是效果的線性乘子。若未設置,值默認是 0。另外除了傳遞百分比,還可以傳遞浮點數,效果是一樣的。
如:
filter: grayscale(1)filter: grayscale(100%)
都可以將節點轉化為 100% 的灰度模式。
所以一切到這里就清楚了,如果我們想要把全站變成灰色,再考慮到各瀏覽器兼容寫法,可以參考下 CSDN 的寫法:
.gray {
-webkit-filter: grayscale(100%);
-moz-filter: grayscale(100%);
-ms-filter: grayscale(100%);
-o-filter: grayscale(100%);
filter: grayscale(100%);
filter: progid:DXImageTransform.Microsoft.BasicImage(grayscale=1);
}
這樣想要變灰的節點只需要加上 gray 這個 class 就好了,比如加到 html 節點上就可以全站變灰了。
最后呢,看一下瀏覽器對 filter 這個樣式的兼容性怎樣,如圖所示:
兼容性
這里我們看到,這里除了 IE,其他的 PC、手機端的瀏覽器都支持了,Firefox 的 PC、安卓端還單獨對 SVG 圖像加了支持,可以放心使用。
本篇文章簡單介紹了一下今天觀察到的網站變灰的實現,也學習了 filter 的更詳細的用法,希望有幫助。
[1] SVG filter element: https://developer.mozilla.org/en-US/docs/Web/SVG/Element/filter
文最初發表于HTTP Toolkit網站,經原作者 Tim Perry 授權由 InfoQ 中文站翻譯分享。
萬物都會有終結,HTTP API 也不例外。不論你的 API 今天看上去多么偉大,遲早有一天你會想發布一個全新的版本,新版本能更好地解決相同問題,在各方面可能都會有所改善,但是它因為有了新參數,與舊版本也無法兼容,或者你只是想徹底關閉舊的 API。總而言之,你現在的 API 不會永遠存在。
但是,這并非輕而易舉就能完成的,因為你的 API 有客戶端。如果你關閉端點、參數或整個 API 而沒有做出恰當的警告的話,那他們肯定會非常不爽。
那么,該怎樣安全地關閉 API,讓你的用戶盡可能地感到輕松愉快呢?
在這方面,我們有正確的做事方式,包括兩個新的頭信息草案,它們正在被新的 IETF “Building Blocks for HTTP APIs”工作組進行標準化,旨在形成一個精確的過程。我們了解一下。
初始第一步:檢查相關的 API 是否真的有客戶端。
希望你能有某些 API 的度量指標,至少在某些地方存有日志。如果沒有的話,那把它們添加上,如果你有這些東西的話,并且你能確定沒有人再使用這個 API 了,那么恭喜你,你贏了。現在,你就可以把它關掉,刪掉代碼,不要再管這篇文章了,好好睡一覺。
下一個問題,如果比較遺憾,你無法去睡覺的話,那就要問問自己,除了關閉這個 API,還有沒有其他方案。你關閉的所有東西都有可能破壞別人的代碼,并且會消耗他們的時間來修復這些問題。如果 API 能繼續運行的話,對客戶端的生態系統和整個 web 的健康都是有好處的。
在很多場景下,舊的 API 可以在內部進行轉換,透明地轉化成對新 API 的調用,這樣可以避免維護兩個完全獨立的版本。這是Stripe的API版本管理方式的一個基本組成部分,他們在所有發生變化的 API 中都包含了轉換,以確保對不兼容的舊版本 API 的請求能繼續像以前那樣運行,根據需要自動轉換請求和響應從而可以使用較新的代碼。
這樣的轉換并不總是可行的,而且如果永遠這樣做的話會帶來明顯的額外復雜性,但是如果你可以做到這一點的話,就能為用戶提供非常有價值的穩定性,并且可以節省大量廢棄舊版本或維護舊版本相關的工作。
但是,如果這個服務/端點/參數已經用到了生產環境,而且繼續支持它是不現實的做法,那么它必須要被淘汰。
要實現這一點,我們就要有一個計劃。我們首先要問自己三個關鍵的問題:
計劃準備就緒之后,我們就該把它告訴人們了。
首先,要把這一決定告訴人們。
發郵件到郵件列表,在 Twitter 或微博上發帖,如果有 API 規范的話,對其進行更新(比如,OpenAPI 在operations和parameters上有一個deprecated字段),并在相關的在線文檔上大聲強調這一點。
你應該包含上文提到的所有信息:他們應該做些什么作為替代方案,你建議他們什么時候開始遷移以及他們必須要進行遷移的最后期限(如果已經確定期限的話)。
在將這些信息告訴給人們后,接下來就該告訴計算機,而這就是新的 IETF 頭信息可以發揮作用的地方。
Deprecation頭信息能告訴客戶端請求的資源現在依然像以前那樣運行,但是這種方式已經不再推薦使用了。通過一個簡單的 HTTP 頭信息,我們就可以聲明這一點:
Deprecation: true
另外,我們還能提供一個日期。這個日期告訴用戶他們何時該開始進行遷移。這個日期可以是一個過去時間(這代表他們應該立即開始遷移),也能是將來時間(通常這意味著他們要遷移到的新環境還沒有準備就緒)。如下所示:
Deprecation: Thu, 21 Jan 2021 23:59:59 GMT
如果你要廢棄整個端點或服務,那么你可以在每個響應中都帶上這樣的頭信息。如果你想要廢棄的是一個具體的特性,可能是一個參數、請求方法或者請求體中的某個特定字段的話,那么你應該在該特性被使用的時候才在響應中包含這個頭信息。
為了給客戶端更多的信息,我們還可以使用Link HTTP 響應頭信息鏈接至端點或人類易讀的文檔。在同一個Link頭信息中,我們可以包含多個這樣的鏈接,只需要使用逗號進行分割即可(后面我們會看到一個完整的例子)。該規范定義了四個與 API 廢棄相關的鏈接:
我們可以為 deprecation 鏈接指向一個人類易于閱讀的描述:
Link: <https://developer.example.com/deprecation>; rel="deprecation"; type="text/html"
這是告訴用戶發生了什么以及他們該怎么辦的主要方式。你應該始終使用它。如果還沒有完整的詳情和最終的關閉日期,那么即使只是一個占位符,這也是很有幫助的。在這種情況下,不要忘記讓用戶訂閱更新,這可以采用郵件列表、RSS 或其他類似的方式來實現。
如果你希望客戶端轉移至 API 相同端點的最新版本,那么可以使用該鏈接指向它,如下所示:
Link: <https://api.example.com/v10/customers>; rel="latest-version"
如果你的 API 有多個可用的版本,通常最好每次向前遷移一個版本,而不是直接從最老的、現已廢棄的版本跳到最新的版本。為了幫助解決這個問題,我們鏈接至已廢棄版本的下一個版本,而不是最新版本,如下所示:
Link: <https://api.example.com/v2/customers>; rel="successor-version"
如果該 API 沒有新的等價版本,用戶最好遷移到一個完全不同的資源,它可能是一個很好的替代方案,那么我們使用 alternate 鏈接來指明這一點,如下所示:
Link: <https://api.example.com/v2/users/123/clients>; rel="alternate"
如果你知道了 API 何時完全關閉的話,那么就應該添加一個Sunset頭信息。
Sunset 頭信息告訴客戶端 API 何時會停止運行。這是一個強制的截止時間:API 客戶端必須要在這個日期前進行遷移,我們承諾在這個時間前不會破壞任何事情。
在這里,我們必須要提供一個時間,它應該是一個未來的時間。不過,如果它是一個過去的時間,這也是可以的:此時就相當于說“這個 API 會在任意時刻關閉,你需要立即停止使用它”。它如下所示:
Sunset: Tue, 20 Jul 2021 23:59:59 GMT
這非常簡單,它不僅可以用到 API 關閉的場景中:我們能用它來標記將來 URL 遷移的 HTTP 重定向,或者表明特定 URL 有限的生命周期(適用于臨時性的內容,或者適用于具有監管要求的特定資源,比如數據保留策略)。它所說明的就是“這個端點可能在該日期后不會再按照你的預期運行,請做好準備”。
該規范也提供了一個 Sunset 鏈接的關系。按照設計,它會鏈接至關于關閉特定端點更加詳細的信息(如果你有 deprecation 鏈接的話,它們可能會是同一個)或者關于服務的通用 Sunset 策略。如下所示:
Link: <http://developer.example.com/our-sunset-policy>;rel="sunset";type="text/html"
在這里我們也要指出,通用的 Sunset 策略是非常有用的!Sunset 策略會告訴客戶端,當我們關閉端點的時候(比如,一年后替代方案上線),用戶該如何確保他們能得知這一情況(郵件列表、狀態頁面、HTTP 頭信息等)以及他們通常應該做些什么(更新、檢查文檔、遵從Link頭信息)。
如果馬上就要廢棄某個 API 的話,添加這樣的鏈接作用其實不大,但是如果你能在一年前就將其發布出去的話,你的客戶端可能已經為此做好了準備。
除此之外,發布 Sunset/Deprecation 策略的最好時間就是現在。如果你恰好正以某種方式編寫 Deprecation 文檔的話,這么做是值得考慮的。
按照設計,這些組成部分能很好地協作。例如,為了表明某個最近廢棄的 API,該 API 會在 6 個月內徹底關閉,我們要鏈接至文檔并提供下一個版本的直接鏈接,那么我們應該在響應中包含如下的頭信息鏈接:
Deprecation: Thu, 21 Jan 2021 23:59:59 GMT
Sunset: Tue, 20 Jul 2021 23:59:59 GMT
Link: <https://api.example.com/v2/customers>; rel="successor-version",
<https://developer.example.com/shutting-down-customers-v1>; rel="deprecation"
如果所有這些都已經準備到位,并且 sunset 截止時間已過,那么我們就可以將 API 關閉了。
但是,這并不意味著你需要立即且徹底消滅該 API。漸進式關閉能有助于確保任何使用該 API 的所有客戶端都有最后的機會在它徹底消失前得到最后一次警告。GitHub 在 2018 年移除一些加密支持的時候曾經這樣做:首先禁用一個小時,然后啟用它,最后在兩周后徹底禁用了它。
這里還有另外一個技巧:安卓在 2015 年為已廢棄的原生API增加了越來越多的延遲,在徹底關閉 API 前,最終達到了 16 秒的等待。
這些漸進式的關閉為那些錯過截止日期的客戶端提供了一些靈活性,并且能幫助那些沒有注意到廢棄時間點的客戶端,從而能在 API 徹底關閉之前處理一些問題。
不管采用哪種方式,只要你盡了最大的努力去溝通關于 API 關閉的事情,那么現在就可以關閉端點/特性/整個服務,刪除代碼,然后睡個好覺。
像這樣小心謹慎地進行廢棄和關閉,可以讓你的客戶端盡可能清楚地知道他們該如何依賴你的 API,何時需要采取行動,以及他們需要做什么。這種變更可能是一件大事兒,這些信息是很重要的。
這些新的草案頭信息讓我們不僅可以與人類溝通,還能將這些信息暴露給自動化系統。隨著這些頭信息的普及,我很高興地開始看到有更多的工具建立在它們之上。通用的 HTTP 客戶端可以根據這些數據自動記錄有用的警告日志,API 生成器本身也能根據 API 規范處理越來越多的問題,而 HTTP 調試器(如HTTP Toolkit)可以在截獲的實時流量中為你突出顯示廢棄端點的使用。這是一個令人激動的時刻,我們可以開始安全地關閉 API 了!
需要注意的是,這些頭信息是 HTTP 規范的草案。在最終確定前,它們有可能會發生變化。也就是說,它們經歷了幾輪修改,從現在開始,它們不太可能發生巨大的變化,現在能廣泛測試它們了。
不過這也意味著還有時間進行反饋! 如果你對它們的工作方式和如何更好地運行有想法的話,請與“Building Blocks for HTTP APIs”工作組聯系。你可以給郵件列表發郵件:httpapi@ietf.org,或者在這里查看之前的郵件列表討論。
原文鏈接:
https://httptoolkit.tech/blog/how-to-turn-off-your-old-apis/
瀏覽器的內核是指支持瀏覽器運行的最核心的程序,分為兩個部分的,一是渲染引擎,另一個是 JS 引擎。渲染引擎在不同的瀏覽器中也不是都相同的。目前市面上常見的瀏覽器內核可以分為這四種:Trident(IE)、Gecko(火狐)、Blink(Chrome、Opera)、Webkit(Safari)。這里面大家最耳熟能詳的可能就是 Webkit 內核了,Webkit 內核是當下瀏覽器世界真正的霸主。
本文我們就以 Webkit 為例,對現代瀏覽器的渲染過程進行一個深度的剖析。
想閱讀更多優質文章請猛戳GitHub 博客。
在介紹瀏覽器渲染過程之前,我們簡明扼要介紹下頁面的加載過程,有助于更好理解后續渲染過程。
要點如下:
例如在瀏覽器輸入https://juejin.im/timeline,然后經過 DNS 解析,juejin.im對應的 IP 是36.248.217.149(不同時間、地點對應的 IP 可能會不同)。然后瀏覽器向該 IP 發送 HTTP 請求。
服務端接收到 HTTP 請求,然后經過計算(向不同的用戶推送不同的內容),返回 HTTP 請求,返回的內容如下:
其實就是一堆 HMTL 格式的字符串,因為只有 HTML 格式瀏覽器才能正確解析,這是 W3C 標準的要求。接下來就是瀏覽器的渲染過程。
瀏覽器渲染過程大體分為如下三部分:
1)瀏覽器會解析三個東西:
一是 HTML/SVG/XHTML,HTML 字符串描述了一個頁面的結構,瀏覽器會把 HTML 結構字符串解析轉換 DOM 樹形結構。
二是 CSS,解析 CSS 會產生 CSS 規則樹,它和 DOM 結構比較像。
三是 Javascript 腳本,等到 Javascript 腳本文件加載后, 通過 DOM API 和 CSSOM API 來操作 DOM Tree 和 CSS Rule Tree。
2)解析完成后,瀏覽器引擎會通過 DOM Tree 和 CSS Rule Tree 來構造 Rendering Tree。
3)最后通過調用操作系統 Native GUI 的 API 繪制。
接下來我們針對這其中所經歷的重要步驟詳細闡述
瀏覽器會遵守一套步驟將 HTML 文件轉換為 DOM 樹。宏觀上,可以分為幾個步驟:
瀏覽器從磁盤或網絡讀取 HTML 的原始字節,并根據文件的指定編碼(例如 UTF-8)將它們轉換成字符串。
在網絡中傳輸的內容其實都是 0 和 1 這些字節數據。當瀏覽器接收到這些字節數據以后,它會將這些字節數據轉換為字符串,也就是我們寫的代碼。
將字符串轉換成 Token,例如:<html>、<body>等。Token 中會標識出當前 Token 是“開始標簽”或是“結束標簽”亦或是“文本”等信息。
這時候你一定會有疑問,節點與節點之間的關系如何維護?
事實上,這就是 Token 要標識“起始標簽”和“結束標簽”等標識的作用。例如“title”Token 的起始標簽和結束標簽之間的節點肯定是屬于“head”的子節點。
上圖給出了節點之間的關系,例如:“Hello”Token 位于“title”開始標簽與“title”結束標簽之間,表明“Hello”Token 是“title”Token 的子節點。同理“title”Token 是“head”Token 的子節點。
事實上,構建 DOM 的過程中,不是等所有 Token 都轉換完成后再去生成節點對象,而是一邊生成 Token 一邊消耗 Token 來生成節點對象。換句話說,每個 Token 被生成后,會立刻消耗這個 Token 創建出節點對象。注意:帶有結束標簽標識的 Token 不會創建節點對象。
接下來我們舉個例子,假設有段 HTML 文本:
復制代碼
<html> <head> <title>Web page parsing</title> </head> <body> <div> <h1>Web page parsing</h1> <p>This is an example Web page.</p> </div> </body> </html>
上面這段 HTML 會解析成這樣:
DOM 會捕獲頁面的內容,但瀏覽器還需要知道頁面如何展示,所以需要構建 CSSOM。
構建 CSSOM 的過程與構建 DOM 的過程非常相似,當瀏覽器接收到一段 CSS,瀏覽器首先要做的是識別出 Token,然后構建節點并生成 CSSOM。
在這一過程中,瀏覽器會確定下每一個節點的樣式到底是什么,并且這一過程其實是很消耗資源的。因為樣式你可以自行設置給某個節點,也可以通過繼承獲得。在這一過程中,瀏覽器得遞歸 CSSOM 樹,然后確定具體的元素到底是什么樣式。
注意:CSS 匹配 HTML 元素是一個相當復雜和有性能問題的事情。所以,DOM 樹要小,CSS 盡量用 id 和 class,千萬不要過渡層疊下去。
當我們生成 DOM 樹和 CSSOM 樹以后,就需要將這兩棵樹組合為渲染樹。
在這一過程中,不是簡單的將兩者合并就行了。渲染樹只會包括需要顯示的節點和這些節點的樣式信息,如果某個節點是 display: none 的,那么就不會在渲染樹中顯示。
我們或許有個疑惑:瀏覽器如果渲染過程中遇到 JS 文件怎么處理?
渲染過程中,如果遇到<script>就停止渲染,執行 JS 代碼。因為瀏覽器有 GUI 渲染線程與 JS 引擎線程,為了防止渲染出現不可預期的結果,這兩個線程是互斥的關系。JavaScript 的加載、解析與執行會阻塞 DOM 的構建,也就是說,在構建 DOM 時,HTML 解析器若遇到了 JavaScript,那么它會暫停構建 DOM,將控制權移交給 JavaScript 引擎,等 JavaScript 引擎運行完畢,瀏覽器再從中斷的地方恢復 DOM 構建。
也就是說,如果你想首屏渲染的越快,就越不應該在首屏就加載 JS 文件,這也是都建議將 script 標簽放在 body 標簽底部的原因。當然在當下,并不是說 script 標簽必須放在底部,因為你可以給 script 標簽添加 defer 或者 async 屬性(下文會介紹這兩者的區別)。
JS 文件不只是阻塞 DOM 的構建,它會導致 CSSOM 也阻塞 DOM 的構建。
原本 DOM 和 CSSOM 的構建是互不影響,井水不犯河水,但是一旦引入了 JavaScript,CSSOM 也開始阻塞 DOM 的構建,只有 CSSOM 構建完畢后,DOM 再恢復 DOM 構建。
這是什么情況?
這是因為 JavaScript 不只是可以改 DOM,它還可以更改樣式,也就是它可以更改 CSSOM。因為不完整的 CSSOM 是無法使用的,如果 JavaScript 想訪問 CSSOM 并更改它,那么在執行 JavaScript 時,必須要能拿到完整的 CSSOM。所以就導致了一個現象,如果瀏覽器尚未完成 CSSOM 的下載和構建,而我們卻想在此時運行腳本,那么瀏覽器將延遲腳本執行和 DOM 構建,直至其完成 CSSOM 的下載和構建。也就是說,在這種情況下,瀏覽器會先下載和構建 CSSOM,然后再執行 JavaScript,最后在繼續構建 DOM。
當瀏覽器生成渲染樹以后,就會根據渲染樹來進行布局(也可以叫做回流)。這一階段瀏覽器要做的事情是要弄清楚各個節點在頁面中的確切位置和大小。通常這一行為也被稱為“自動重排”。
布局流程的輸出是一個“盒模型”,它會精確地捕獲每個元素在視口內的確切位置和尺寸,所有相對測量值都將轉換為屏幕上的絕對像素。
布局完成后,瀏覽器會立即發出“Paint Setup”和“Paint”事件,將渲染樹轉換成屏幕上的像素。
以上我們詳細介紹了瀏覽器工作流程中的重要步驟,接下來我們討論幾個相關的問題:
1.async 和 defer 的作用是什么?有什么區別?
接下來我們對比下 defer 和 async 屬性的區別:
其中藍色線代表 JavaScript 加載;紅色線代表 JavaScript 執行;綠色線代表 HTML 解析。
1)情況 1<script src="script.js"></script>
沒有 defer 或 async,瀏覽器會立即加載并執行指定的腳本,也就是說不等待后續載入的文檔元素,讀到就加載并執行。
2)情況 2<script async src="script.js"></script> (異步下載)
async 屬性表示異步執行引入的 JavaScript,與 defer 的區別在于,如果已經加載好,就會開始執行——無論此刻是 HTML 解析階段還是 DOMContentLoaded 觸發之后。需要注意的是,這種方式加載的 JavaScript 依然會阻塞 load 事件。換句話說,async-script 可能在 DOMContentLoaded 觸發之前或之后執行,但一定在 load 觸發之前執行。
3)情況 3 <script defer src="script.js"></script>(延遲執行)
defer 屬性表示延遲執行引入的 JavaScript,即這段 JavaScript 加載時 HTML 并未停止解析,這兩個過程是并行的。整個 document 解析完畢且 defer-script 也加載完成之后(這兩件事情的順序無關),會執行所有由 defer-script 加載的 JavaScript 代碼,然后觸發 DOMContentLoaded 事件。
defer 與相比普通 script,有兩點區別:載入 JavaScript 文件時不阻塞 HTML 的解析,執行階段被放到 HTML 標簽解析完成之后。
在加載多個 JS 腳本的時候,async 是無順序的加載,而 defer 是有順序的加載。
2. 為什么操作 DOM 慢?
把 DOM 和 JavaScript 各自想象成一個島嶼,它們之間用收費橋梁連接。——《高性能 JavaScript》
JS 是很快的,在 JS 中修改 DOM 對象也是很快的。在 JS 的世界里,一切是簡單的、迅速的。但 DOM 操作并非 JS 一個人的獨舞,而是兩個模塊之間的協作。
因為 DOM 是屬于渲染引擎中的東西,而 JS 又是 JS 引擎中的東西。當我們用 JS 去操作 DOM 時,本質上是 JS 引擎和渲染引擎之間進行了“跨界交流”。這個“跨界交流”的實現并不簡單,它依賴了橋接接口作為“橋梁”(如下圖)。
過“橋”要收費——這個開銷本身就是不可忽略的。我們每操作一次 DOM(不管是為了修改還是僅僅為了訪問其值),都要過一次“橋”。過“橋”的次數一多,就會產生比較明顯的性能問題。因此“減少 DOM 操作”的建議,并非空穴來風。
3. 你真的了解回流和重繪嗎?
渲染的流程基本上是這樣(如下圖黃色的四個步驟):
1. 計算 CSS 樣式
2. 構建 Render Tree
3.Layout – 定位坐標和大小
4. 正式開畫
注意:上圖流程中有很多連接線,這表示了 Javascript 動態修改了 DOM 屬性或是 CSS 屬性會導致重新 Layout,但有些改變不會重新 Layout,就是上圖中那些指到天上的箭頭,比如修改后的 CSS rule 沒有被匹配到元素。
這里重要要說兩個概念,一個是 Reflow,另一個是 Repaint
重繪:當我們對 DOM 的修改導致了樣式的變化、卻并未影響其幾何屬性(比如修改了顏色或背景色)時,瀏覽器不需重新計算元素的幾何屬性、直接為該元素繪制新的樣式(跳過了上圖所示的回流環節)。
回流:當我們對 DOM 的修改引發了 DOM 幾何尺寸的變化(比如修改元素的寬、高或隱藏元素等)時,瀏覽器需要重新計算元素的幾何屬性(其他元素的幾何屬性和位置也會因此受到影響),然后再將計算的結果繪制出來,這個過程就是回流(也叫重排)。
我們知道,當網頁生成的時候,至少會渲染一次。在用戶訪問的過程中,還會不斷重新渲染。重新渲染會重復回流 + 重繪或者只有重繪。
回流必定會發生重繪,重繪不一定會引發回流。重繪和回流會在我們設置節點樣式時頻繁出現,同時也會很大程度上影響性能。回流所需的成本比重繪高的多,改變父節點里的子節點很可能會導致父節點的一系列回流。
1)常見引起回流屬性和方法
任何會改變元素幾何信息 (元素的位置和尺寸大小) 的操作,都會觸發回流,
2)常見引起重繪屬性和方法
3)如何減少回流、重繪
復制代碼
for(let i = 0; i < 1000; i++) { // 獲取 offsetTop 會導致回流,因為需要去獲取正確的值 console.log(document.querySelector('.test').style.offsetTop) }
基于上面介紹的瀏覽器渲染原理,DOM 和 CSSOM 結構構建順序,初始化可以對頁面渲染做些優化,提升頁面性能。
綜上所述,我們得出這樣的結論:
參考文章
更多內容,請關注前端之巔。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。