補充:JS中其實是沒有線程概念的,所謂的單線程也只是相對于多線程而言。JS的設(shè)計初衷就沒有考慮這些,針對JS這種不具備并行任務(wù)處理的特性,我們稱之為“單線程”。
JS單線程是指一個瀏覽器進(jìn)程中只有一個JS的執(zhí)行線程,同一時刻內(nèi)只會有一段代碼在執(zhí)行。
舉個通俗例子,假設(shè)JS支持多線程操作的話,JS可以操作DOM,那么一個線程在刪除DOM,另外一個線程就在獲取DOM數(shù)據(jù),這樣子明顯不合理,這算是證明之一。
來看段代碼??
function foo() { ? ?console.log("first");
setTimeout(( function(){ ? ? ? ?console.log( 'second' );
}),5);
}
for (var i = 0; i < 1000000; i++) {
foo();
}復(fù)制代碼
打印結(jié)果就是首先是很多個first,然后再是second。
異步機(jī)制是瀏覽器的兩個或以上常駐線程共同完成的,舉個例子,比如異步請求由兩個常駐線程,JS執(zhí)行線程和事件觸發(fā)線程共同完成的。
先給出結(jié)論
CSS
不會阻塞DOM
解析,但會阻塞DOM
渲染。CSS
會阻塞JS執(zhí)行,并不會阻塞JS文件下載CSS
控制的屬性,瀏覽器是需要計算的,也就是依賴于CSS
。瀏覽器也無法感知腳本內(nèi)容到底是什么,為避免樣式獲取,因而只好等前面所有的樣式下載完后,再執(zhí)行JS
。先給出結(jié)論??
由于 JavaScript 是可操縱 DOM 的,如果在修改這些元素屬性同時渲染界面(即 JavaScript 線程和 UI 線程同時運行),那么渲染線程前后獲得的元素數(shù)據(jù)就可能不一致了。 因此為了防止渲染出現(xiàn)不可預(yù)期的結(jié)果,瀏覽器設(shè)置?「GUI 渲染線程與 JavaScript 引擎為互斥」的關(guān)系。 當(dāng) JavaScript 引擎執(zhí)行時 GUI 線程會被掛起,GUI 更新會被保存在一個隊列中等到引擎線程空閑時立即被執(zhí)行。 當(dāng)瀏覽器在執(zhí)行 JavaScript 程序的時候,GUI 渲染線程會被保存在一個隊列中,直到 JS 程序執(zhí)行完成,才會接著執(zhí)行。 因此如果 JS 執(zhí)行的時間過長,這樣就會造成頁面的渲染不連貫,導(dǎo)致頁面渲染加載阻塞的感覺。
DOMContentLoaded
?事件前執(zhí)行,如果缺少?src
?屬性(即內(nèi)嵌腳本),該屬性不應(yīng)被使用,因為這種情況下它不起作用帶async的腳本一定會在load事件之前執(zhí)行,可能會在DOMContentLoaded之前或之后執(zhí)行。
如果 script 標(biāo)簽中包含 defer,那么這一塊腳本將不會影響 HTML 文檔的解析,而是等到HTML 解析完成后才會執(zhí)行。而 DOMContentLoaded 只有在 defer 腳本執(zhí)行結(jié)束后才會被觸發(fā)。
我覺得這個題目說法上可能就是行不通,不能這么說,如果了解的話,都知道will-change只是一個優(yōu)化的手段,使用JS改變transform也可以享受這個屬性帶來的變化,所以這個說法上有點不妥。
所以圍繞這個問題展開話,更應(yīng)該說建議推薦使用CSS動畫,至于為什么呢,涉及的知識點大概就是重排重繪,合成,這方面的點,我在瀏覽器渲染流程中也提及了。
盡可能的避免重排和重繪,具體是哪些操作呢,如果非要去操作JS實現(xiàn)動畫的話,有哪些優(yōu)化的手段呢?
比如??
createDocumentFragment
進(jìn)行批量的 DOM 操作節(jié)流的意思是讓函數(shù)有節(jié)制地執(zhí)行,而不是毫無節(jié)制的觸發(fā)一次就執(zhí)行一次。什么叫有節(jié)制呢?就是在一段時間內(nèi),只執(zhí)行一次。
規(guī)定在一個單位時間內(nèi),只能觸發(fā)一次函數(shù)。如果這個單位時間內(nèi)觸發(fā)多次函數(shù),只有一次生效。
抓取一個關(guān)鍵的點:就是執(zhí)行的時機(jī)。要做到控制執(zhí)行的時機(jī),我們可以通過「一個開關(guān)」,與定時器setTimeout結(jié)合完成。
?function throttle(fn, delay) { ? ? ? ? ? ?let flag = true,
timer = null; ? ? ? ? ? ?return function (...args) { ? ? ? ? ? ? ? ?let context = this; ? ? ? ? ? ? ? ?if (!flag) return;
flag = false;
clearTimeout(timer)
timer = setTimeout(() => {
fn.apply(context, args);
flag = true;
}, delay);
};
};復(fù)制代碼
在事件被觸發(fā)n秒后再執(zhí)行回調(diào),如果在這n秒內(nèi)又被觸發(fā),則重新計時。
核心思想:每次事件觸發(fā)都會刪除原有定時器,建立新的定時器。通俗意思就是反復(fù)觸發(fā)函數(shù),只認(rèn)最后一次,從最后一次開始計時。
代碼:
?function debounce(fn, delay) { ? ? ? ? ? ?let timer = null
return function (...args) { ? ? ? ? ? ? ? ?let context = this
if(timer) ? clearTimeout(timer)
timer = setTimeout(function() {
fn.apply(context, args)
},delay)
}
}復(fù)制代碼
_.debounce
?和?_.throttle
?方法,可以使用 Lodash 的自定義構(gòu)建工具,生成一個 2KB 的壓縮庫。使用以下的簡單命令即可:npm i -g lodash-cli
npm i -g lodash-clilodash-cli include=debounce,throttle復(fù)制代碼
_.debounce
?方法:// 錯誤$(window).on('scroll', function() {
_.debounce(doSomething, 300);
});// 正確$(window).on('scroll', _.debounce(doSomething, 200));復(fù)制代碼
debounced_version.cancel()
,lodash 和 underscore.js 都有效。let debounced_version = _.debounce(doSomething, 200);
$(window).on(‘scroll’, debounced_version);// 如果需要的話debounced_version.cancel();復(fù)制代碼防抖
節(jié)流
動畫幀率可以作為衡量標(biāo)準(zhǔn),一般來說畫面在 60fps 的幀率下效果比較好。
換算一下就是,每一幀要在 16.7ms (16.7 = 1000/60) 內(nèi)完成渲染。
我們來看看MDN對它的解釋吧??
window.requestAnimationFrame() 方法告訴瀏覽器您希望執(zhí)行動畫并請求瀏覽器在下一次重繪之前調(diào)用指定的函數(shù)來更新動畫。該方法使用一個回調(diào)函數(shù)作為參數(shù),這個回調(diào)函數(shù)會在瀏覽器重繪之前調(diào)用。— MDN
當(dāng)我們調(diào)用這個函數(shù)的時候,我們告訴它需要做兩件事:
rAF(requestAnimationFrame) 最大的優(yōu)勢是「由系統(tǒng)來決定回調(diào)函數(shù)的執(zhí)行時機(jī)」。
具體一點講就是,系統(tǒng)每次繪制之前會主動調(diào)用 rAF 中的回調(diào)函數(shù),如果系統(tǒng)繪制率是 60Hz,那么回調(diào)函數(shù)就每16.7ms 被執(zhí)行一次,如果繪制頻率是75Hz,那么這個間隔時間就變成了 1000/75=13.3ms。
換句話說就是,rAF 的執(zhí)行步伐跟著系統(tǒng)的繪制頻率走。它能保證回調(diào)函數(shù)在屏幕每一次的繪制間隔中只被執(zhí)行一次(上一個知識點剛剛梳理完「函數(shù)節(jié)流」),這樣就不會引起丟幀現(xiàn)象,也不會導(dǎo)致動畫出現(xiàn)卡頓的問題。
另外它可以自動調(diào)節(jié)頻率。如果callback工作太多無法在一幀內(nèi)完成會自動降低為30fps。雖然降低了,但總比掉幀好。
與setTimeout動畫對比的話,有以下幾點優(yōu)勢
規(guī)范中似乎是這么去定義的:
這樣子分析的話,似乎很合理嘛,為什么要在重新渲染前去調(diào)用呢?因為rAF作為官方推薦的一種做流暢動畫所應(yīng)該使用的API,做動畫不可避免的去操作DOM,而如果是在渲染后去修改DOM的話,那就只能等到下一輪渲染機(jī)會的時候才能去繪制出來了,這樣子似乎不合理。
rAF
在瀏覽器決定渲染之前給你最后一個機(jī)會去改變 DOM 屬性,然后很快在接下來的繪制中幫你呈現(xiàn)出來,所以這是做流暢動畫的不二選擇。
至于宏任務(wù),微任務(wù),這可以說起來就要展開篇幅了,暫時不在這里梳理了。
跟?_.throttle(dosomething, 16)
?等價。它是高保真的,如果追求更好的精確度的話,可以用瀏覽器原生的 API 。
可以使用 rAF API 替換 throttle 方法,考慮一下優(yōu)缺點:
優(yōu)點
缺點
根據(jù)經(jīng)驗,如果 JavaScript 方法需要繪制或者直接改變屬性,我會選擇?requestAnimationFrame
,只要涉及到重新計算元素位置,就可以使用它。
涉及到 AJAX 請求,添加/移除 class (可以觸發(fā) CSS 動畫),我會選擇?_.debounce
?或者?_.throttle
?,可以設(shè)置更低的執(zhí)行頻率(例子中的200ms 換成16ms)。