海闊憑魚躍,天高任鳥飛,Hey 你好!我是秦愛德,😄
導讀
就在前不久,同事寫了一個拖拽左邊選單欄改變選單欄寬度從而得到更好的互動體驗效果,But ! 美中不足的是拖拽的時候如果手速過快,會導致卡頓效果,看起來十分難受,經過不斷除錯,最終是使用了setTimeout解決了該問題,那么問題來了!為什么setTimeout能解決影片卡頓問題呢?
一圖勝千文,通過兩張圖的前后對比,我們來看看實際效果!
(這是沒有加上setTimeout之前,有明顯卡頓😔😔😔)

(這是加上setTimeout之后,拖拽效果明顯順暢了不少😁😁😁)

假設提問:會不會是內容過多導致的呢?😲
為了證實我這個猜想,故將內容切換到比較少模塊,
果然,拖拽效果瞬間流暢了許多😆😆😆

這個時候基本可以確認是內容過多導致的卡頓,那如何保證不改變內容大小的情況下達到拖拽順滑的效果呢?
提出解決方案:能否通過減少回流次數來節省性能,從而使得拖拽順滑呢?😲
為此,故將右邊內容固定寬度,拖拽選單的時候,由于右邊內容盒子寬度已經固定,所以不會造成元素盒子尺寸發生變化,故不會觸發回流,
果然,拖拽效果再次流暢了許多😆😆😆

什么是回流與重繪?
回流: 當我們對 DOM 的修改引發了 DOM 幾何尺寸的變化(比如修改元素的寬、高或隱藏元素等)時,瀏覽器需要重新計算元素的幾何屬性(其他元素的幾何屬性和位置也會因此受到影響),然后再將計算的結果繪制出來,這個程序就是回流(也叫重排),
重繪: 當我們對 DOM 的修改導致了樣式的變化、卻并未影響其幾何屬性(比如修改了顏色或背景色)時,瀏覽器不需重新計算元素的幾何屬性、直接為該元素繪制新的樣式,這個程序叫做重繪,
由此可知:重繪不一定導致回流,回流一定會導致重繪!
通過控制內容寬度,減少回流操作確實解決了卡頓問題,但該操作導致頁面無法自適應了,無法適配各種尺寸的螢屏,那么還有什么辦法是可以既不改變內容大小,也不改變內容盒子尺寸從而解決選單拖拽卡頓問題呢?
能否通過打亂模塊宣告周期,改變任務執行順序來使得拖拽順滑呢?😲
如標題所示,當我們將改變元素尺寸的代碼放置到setTimeout中,利用setTimeout實作一種偽多執行緒的效果,從而解決了拖拽卡頓問題!
關于這一點,涉及到的背后知識還是挺多,莫慌!請接著往下看👇👇👇
瀏覽器渲染程序(簡述)
當我們在瀏覽器輸入網址到頁面呈現,在瀏覽器渲染這一層面,大體上需要經歷三個步驟(下載 👉 決議 👉 布局):
1:根據頁面URL向服務器發送一個請求,服務器回應頁面的HTML,瀏覽器首先去下載html,js,css,圖片,字體等一系列資源,
2:瀏覽器將html決議成為dom tree(節點樹),將css決議成為rule tree(規則樹),二者結合成為render tree(渲染樹)
3:渲染樹構建完畢之后,瀏覽器的渲染引擎將遍歷渲染樹把每個節點繪制出來,從而呈現出我們所看到的頁面效果,
瀏覽器重繪的頻率大概是60次/秒, 也就是說重繪一次大概時間為16ms,如果瀏覽器對每一幀的渲染作業超過了這個時間, 頁面的渲染就會出現卡頓的現象,
瀏覽器行程與執行緒(簡述)
我們都知道JavaScript是單執行緒的,但是瀏覽器是支持多行程啊,所以,配合瀏覽器的合理分配,各個行程各司其職,玩兒的不亦樂乎,
什么是行程?
行程是CPU進行資源分配的基本單位
什么是執行緒?
執行緒是CPU調度的最小單位,是建立在行程的基礎上運行的單位,共享行程的記憶體空間,
瀏覽器包含了那些行程?
1:瀏覽器行程(負責瀏覽器級別的操作,如:標簽頁的創建和銷毀、資源的管理與下載等)
2:第三方插件行程(負責第三方插件的使用)
3:GPU行程(負責3D繪制和硬體加速)
4:瀏覽器渲染行程(瀏覽器內核負責HTML,CSS,JS等檔案的決議和執行)
瀏覽器渲染行程(瀏覽器內核詳解)
瀏覽器渲染行程主要包含了5個執行緒:
1:GUI渲染執行緒
2:JS引擎執行緒
3:計時器執行緒
4:定時器觸發執行緒
5:異步Http請求執行緒
GUI渲染執行緒與JS引擎執行緒是相互排斥的,因為二者不能夠同時進行,試想一下,渲染執行緒這邊正在渲染一個div的背景色,JS引擎執行緒那邊可勁兒的通過js改變你這個div元素的背景色以及尺寸啥的,那么這時候到底以那一邊為標準,非得亂套不可,所以,當執行其中任何一個執行緒的時候,另一個執行緒操作則被掛起,等待其他任務執行完畢之后,再回來執行這邊的任務,
了解了這些前置知識后,我們還需要了解一下宏任務微任務,以及事件回圈、任務佇列等知識,這里我不做過多講述,可閱讀
Jiasm 寫的非常好的文章 ! 傳送門-走你🚀🚀🚀
關于setTimeout
setTimeout函式用來指定某個函式或某段代碼,在多少毫秒之后執行,
setTimeout會將指定的代碼移出本次執行,等到下一輪Event Loop時,再檢查是否到了指定時間,如果到了,就執行對應的代碼;如果不到,就等到再下一輪Event Loop時重新判斷,這就說明setTimeout指定的代碼,必須等到本次執行的所有代碼都執行完,才會執行,
setTimeout掃盲:
1: setTimeout中的this關鍵字將指向全域環境,而不是定義時所在的那個物件,
2: setTimeout是一個異步任務,并且是一個宏任務,所以是最后執行的任務,
3: setTimeout執行回呼間隔時間長度
setTimeout( () =>{
console.log('hello');
}, 100)
注意:這里的時間間隔長度一定是大于100ms的,具體大于多少取決于在此之前同步任務執行的js需要占用多少時間,
4: setTimeout(func,0)與setTimeout(func)的區別
setTimeout(func,0):表示讓func回呼函式掛起,等到所有同步任務以及任務佇列里面的任務執行完畢之后,立馬執行func函式,事實上,0毫秒實際上達不到的,setTimeout推遲執行的時間,最少是4毫秒,
setTimeout(func):如果沒有傳遞具體延遲時間,瀏覽器將為定時器自動分配時間,具體分配多少因瀏覽器而異,
5: setTimeout的延遲時間最大值是多少呢?
答案是:2147483647毫秒(24.8天),超出這個時間,將作為溢位處理,即等同于setTimeout(f,0)的效果,
6: setTimeout將回傳一個表示計數器編號的整數值,將該整數傳入clearTimeout就可以取消對應的定時器,
-------------- 華麗的分割線 --------------
秦愛德你說了這么多,這跟拖拽卡頓有啥關系啊?🌚🌚🌚
哈哈,你別慌,咱們一步一步來,接下來我就給你介紹一波setTimeout的實際用途!🌝🌝🌝
用途以一:調整事件的發生順序
setTimeout( () =>{
console.log(2);
},0)
console.log(1);
我們都知道js在決議代碼的時候是逐行執行的,只有當遇到異步任務的時候,才會將異步任務掛起放到同步任務之后執行,所以我們正好利用這個特性,來打亂事件執行順序,以滿足特定的需求場景,
用途二:分割耗時任務
我們都知道javascript是單執行緒的,其特點就是容易出現阻塞,如果某一段程式處理時間很長,很容易導致整個頁面卡住,干不了其他事兒,這時候便可以使用setTimeout來將這些任務分割從而解決此類問題,
用我們拖拽改變元素寬度舉個栗子🌰
...其它代碼
document.onmousemove = function (e) {
...其它代碼
setTimeout( () =>{
document.getElementById("app").style.width = xxx + 'px'
},0)
}
如上代碼,我們在拖拽元素的程序中,頻繁的觸發回流操作,而回流操作本身就是非常昂貴的,這個時候就很容易出現阻塞,導致網頁卡頓,這時我們加上setTimeout,巧妙的將這部分操作進行一個分割處理,將任務放到瀏覽器最早可得的空閑時段執行,那些計算量大、耗時長的任務,分別放到setTimeout(f,0)里面執行(分片塞入佇列),這樣即使在復雜程式沒有處理完時,我們操作頁面,也是能得到即時回應的,
用途三:事件防抖與節流
關于這一部分的內容,如要細說又是長篇大論了,這里直接推薦閱讀
小不點別打我寫的非常不錯的文章
傳送門-走你🚀🚀🚀
好啦!關于一個拖拽卡頓問題引發出對setTimeOut的探索就簡單的介紹到這里了,由此可見,一個小小的問題,背后也需要強大的理論知識支撐,所以,掌握好js的一些底層原理知識,才是我們提升個人能力,進入下一個階段的關鍵步驟,這里安利一句我特別喜歡的名句:
80%的問題可以用20%的知識解決,而剩下的那20%的問題則需要你用80%的知識積累去解決了,
感謝
點贊👍再看,已成習慣!該系列持續更新,你們的一鍵三連就是我持續寫作的最大動力,如果對本篇博客有任何意見和建議,歡迎小伙伴們留言!歡迎來擾!😜😝
本文首發于公眾號「前端秦愛德」,歡迎關注,
感興趣的小伙伴還可以加入我的「秦愛德前端交流群」,我在成都上班兒,群里有大批成都本土優秀大佬以及美女HR,如果你碰巧也是成都的,那就趕快進來吧,
我是秦愛德,一個在互聯網夾縫求生的程式員!歡迎關注我的個人公眾號“前端秦愛德”
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/263395.html
標籤:AI
