日本国产欧美大码A视频 _国产高颜值极品在线视频_色偷偷亚洲第一综合网_国产精品一二三社区视频_久久久青草视频

IT培訓(xùn)-高端面授IT培訓(xùn)機(jī)構(gòu)
云和教育:云和數(shù)據(jù)集團(tuán)高端IT職業(yè)教育品牌
  • 國(guó)家級(jí)
    全民數(shù)字素養(yǎng)與技能培訓(xùn)基地
  • 河南省
    第一批產(chǎn)教融合型企業(yè)建設(shè)培育單位
  • 鄭州市
    數(shù)字技能人才(碼農(nóng))培養(yǎng)評(píng)價(jià)聯(lián)盟

收藏 | 10道瀏覽器面試題解析

  • 發(fā)布時(shí)間:
    2020-12-16
  • 版權(quán)所有:
    云和教育
  • 分享:
前言
Preface

想要成為一名合格的前端工程師,掌握相關(guān)瀏覽器的工作原理是必備的,這樣子才會(huì)有一個(gè)完整知識(shí)體系,要是「能參透瀏覽器的工作原理,你就能解決80%的前端難題」。
今天總結(jié)了10道瀏覽器面試題及解析,作為前端開發(fā)工程師的你趕緊來(lái)看看吧!

1. 常見的瀏覽器內(nèi)核有哪些?
2. 瀏覽器的主要組成部分是什么?

  1. 「用戶界面」?– 包括地址欄、前進(jìn)/后退按鈕、書簽菜單等。
  2. 「瀏覽器引擎」?– 在用戶界面和呈現(xiàn)引擎之間傳送指令。
  3. 「呈現(xiàn)引擎」?– 負(fù)責(zé)顯示請(qǐng)求的內(nèi)容。如果請(qǐng)求的內(nèi)容是 HTML,它就負(fù)責(zé)解析 HTML 和 CSS 內(nèi)容,并將解析后的內(nèi)容顯示在屏幕上。
  4. 「網(wǎng)絡(luò)」?– 用于網(wǎng)絡(luò)調(diào)用,比如 HTTP 請(qǐng)求。
  5. 「用戶界面后端」?-用于繪制基本的窗口小部件,比如組合框和窗口。
  6. 「JavaScript 解釋器」– 用于解析和執(zhí)行 JavaScript 代碼。
  7. 「數(shù)據(jù)存儲(chǔ)」?– 這是持久層。瀏覽器需要在硬盤上保存各種數(shù)據(jù),例如 Cookie。新的 HTML 規(guī)范 (HTML5) 定義了“網(wǎng)絡(luò)數(shù)據(jù)庫(kù)”,這是一個(gè)完整(但是輕便)的瀏覽器內(nèi)數(shù)據(jù)庫(kù)。
值得注意的是,和大多數(shù)瀏覽器不同,Chrome 瀏覽器的每個(gè)標(biāo)簽頁(yè)都分別對(duì)應(yīng)一個(gè)呈現(xiàn)引擎實(shí)例。每個(gè)標(biāo)簽頁(yè)都是一個(gè)獨(dú)立的進(jìn)程。
3. 為什么JavaScript是單線程的,與異步?jīng)_突嗎

補(bǔ)充:JS中其實(shí)是沒(méi)有線程概念的,所謂的單線程也只是相對(duì)于多線程而言。JS的設(shè)計(jì)初衷就沒(méi)有考慮這些,針對(duì)JS這種不具備并行任務(wù)處理的特性,我們稱之為“單線程”。

JS單線程是指一個(gè)瀏覽器進(jìn)程中只有一個(gè)JS的執(zhí)行線程,同一時(shí)刻內(nèi)只會(huì)有一段代碼在執(zhí)行。

舉個(gè)通俗例子,假設(shè)JS支持多線程操作的話,JS可以操作DOM,那么一個(gè)線程在刪除DOM,另外一個(gè)線程就在獲取DOM數(shù)據(jù),這樣子明顯不合理,這算是證明之一。

來(lái)看段代碼??

function foo() { ? ?console.log("first");
setTimeout(( function(){ ? ? ? ?console.log( 'second' );
}),5);
}
for (var i = 0; i < 1000000; i++) {
foo();
}復(fù)制代碼

打印結(jié)果就是首先是很多個(gè)first,然后再是second。

異步機(jī)制是瀏覽器的兩個(gè)或以上常駐線程共同完成的,舉個(gè)例子,比如異步請(qǐng)求由兩個(gè)常駐線程,JS執(zhí)行線程和事件觸發(fā)線程共同完成的。

  • JS執(zhí)行線程發(fā)起異步請(qǐng)求(瀏覽器會(huì)開啟一個(gè)HTTP請(qǐng)求線程來(lái)執(zhí)行請(qǐng)求,這時(shí)JS的任務(wù)完成,繼續(xù)執(zhí)行線程隊(duì)列中剩下任務(wù))
  • 然后在未來(lái)的某一時(shí)刻事件觸發(fā)線程監(jiān)視到之前的發(fā)起的HTTP請(qǐng)求已完成,它就會(huì)把完成事件插入到JS執(zhí)行隊(duì)列的尾部等待JS處理
再比如定時(shí)器觸發(fā)(settimeout和setinterval) 是由「瀏覽器的定時(shí)器線程」執(zhí)行的定時(shí)計(jì)數(shù),然后在定時(shí)時(shí)間把定時(shí)處理函數(shù)的執(zhí)行請(qǐng)求插入到JS執(zhí)行隊(duì)列的尾端(所以用這兩個(gè)函數(shù)的時(shí)候,實(shí)際的執(zhí)行時(shí)間是大于或等于指定時(shí)間的,不保證能準(zhǔn)確定時(shí)的)。
所以這么說(shuō),JS單線程與異步更多是瀏覽器行為,之間不沖突。
4. CSS加載會(huì)造成阻塞嗎

先給出結(jié)論

  • CSS不會(huì)阻塞DOM解析,但會(huì)阻塞DOM渲染。
  • CSS會(huì)阻塞JS執(zhí)行,并不會(huì)阻塞JS文件下載
先講一講CSSOM作用
  • 第一個(gè)是提供給 JavaScript 操作樣式表的能力
  • 第二個(gè)是為布局樹的合成提供基礎(chǔ)的樣式信息
  • 這個(gè) CSSOM 體現(xiàn)在 DOM 中就是document.styleSheets。
由之前講過(guò)的瀏覽器渲染流程我們可以看出:
DOM 和 CSSOM通常是并行構(gòu)建的,所以「CSS 加載不會(huì)阻塞 DOM 的解析」。
然而由于Render Tree 是依賴DOM Tree和 CSSOM Tree的,所以它必須等到兩者都加載完畢后,完成相應(yīng)的構(gòu)建,才開始渲染,因此,「CSS加載會(huì)阻塞DOM渲染」。
由于 JavaScript 是可操縱 DOM 和 css 樣式 的,如果在修改這些元素屬性同時(shí)渲染界面(即 JavaScript 線程和 UI 線程同時(shí)運(yùn)行),那么渲染線程前后獲得的元素?cái)?shù)據(jù)就可能不一致了。
因此為了防止渲染出現(xiàn)不可預(yù)期的結(jié)果,瀏覽器設(shè)置?「GUI 渲染線程與 JavaScript 引擎為互斥」的關(guān)系。
有個(gè)需要注意的點(diǎn)就是:
「有時(shí)候JS需要等到CSS的下載,這是為什么呢?」
仔細(xì)思考一下,其實(shí)這樣做是有道理的,如果腳本的內(nèi)容是獲取元素的樣式,寬高等CSS控制的屬性,瀏覽器是需要計(jì)算的,也就是依賴于CSS。瀏覽器也無(wú)法感知腳本內(nèi)容到底是什么,為避免樣式獲取,因而只好等前面所有的樣式下載完后,再執(zhí)行JS。
JS文件下載和CSS文件下載是并行的,有時(shí)候CSS文件很大,所以JS需要等待。
因此,樣式表會(huì)在后面的 js 執(zhí)行前先加載執(zhí)行完畢,所以「css 會(huì)阻塞后面 js 的執(zhí)行」。
5. 為什么JS會(huì)阻塞頁(yè)面加載

先給出結(jié)論??

  • 「JS阻塞DOM解析」,也就會(huì)阻塞頁(yè)面
這也是為什么說(shuō)JS文件放在最下面的原因,那為什么會(huì)阻塞DOM解析呢
你可以這樣子理解:
由于 JavaScript 是可操縱 DOM 的,如果在修改這些元素屬性同時(shí)渲染界面(即 JavaScript 線程和 UI 線程同時(shí)運(yùn)行),那么渲染線程前后獲得的元素?cái)?shù)據(jù)就可能不一致了。
因此為了防止渲染出現(xiàn)不可預(yù)期的結(jié)果,瀏覽器設(shè)置?「GUI 渲染線程與 JavaScript 引擎為互斥」的關(guān)系。
當(dāng) JavaScript 引擎執(zhí)行時(shí) GUI 線程會(huì)被掛起,GUI 更新會(huì)被保存在一個(gè)隊(duì)列中等到引擎線程空閑時(shí)立即被執(zhí)行。
當(dāng)瀏覽器在執(zhí)行 JavaScript 程序的時(shí)候,GUI 渲染線程會(huì)被保存在一個(gè)隊(duì)列中,直到 JS 程序執(zhí)行完成,才會(huì)接著執(zhí)行。
因此如果 JS 執(zhí)行的時(shí)間過(guò)長(zhǎng),這樣就會(huì)造成頁(yè)面的渲染不連貫,導(dǎo)致頁(yè)面渲染加載阻塞的感覺(jué)。
另外,如果 JavaScript 文件中沒(méi)有操作 DOM 相關(guān)代碼,就可以將該 JavaScript 腳本設(shè)置為異步加載,通過(guò) async 或 defer 來(lái)標(biāo)記代碼。
6. defer 和 async 的區(qū)別 ?
  • 兩者都是異步去加載外部JS文件,不會(huì)阻塞DOM解析
  • Async是在外部JS加載完成后,瀏覽器空閑時(shí),Load事件觸發(fā)前執(zhí)行,標(biāo)記為async的腳本并不保證按照指定他們的先后順序執(zhí)行,該屬性對(duì)于內(nèi)聯(lián)腳本無(wú)作用 (即沒(méi)有「src」屬性的腳本)。
  • defer是在JS加載完成后,整個(gè)文檔解析完成后,觸發(fā)?DOMContentLoaded?事件前執(zhí)行,如果缺少?src?屬性(即內(nèi)嵌腳本),該屬性不應(yīng)被使用,因?yàn)檫@種情況下它不起作用
7. DOMContentLoaded 與 load 的區(qū)別 ?

  • DOMContentLoaded事件觸發(fā)時(shí):僅當(dāng)DOM解析完成后,不包括樣式表,圖片等資源。
  • onload 事件觸發(fā)時(shí),頁(yè)面上所有的 DOM,樣式表,腳本,圖片等資源已經(jīng)加載完畢。
那么也就是先DOMContentLoaded -> load,那么在Jquery中,使用(document).load(callback)監(jiān)聽的就是load事件。
那我們可以聊一聊它們與async和defer區(qū)別
帶async的腳本一定會(huì)在load事件之前執(zhí)行,可能會(huì)在DOMContentLoaded之前或之后執(zhí)行。
  • 情況1:HTML 還沒(méi)有被解析完的時(shí)候,async腳本已經(jīng)加載完了,那么 HTML 停止解析,去執(zhí)行腳本,腳本執(zhí)行完畢后觸發(fā)DOMContentLoaded事件
  • 情況2:HTML 解析完了之后,async腳本才加載完,然后再執(zhí)行腳本,那么在HTML解析完畢、async腳本還沒(méi)加載完的時(shí)候就觸發(fā)DOMContentLoaded事件
如果 script 標(biāo)簽中包含 defer,那么這一塊腳本將不會(huì)影響 HTML 文檔的解析,而是等到HTML 解析完成后才會(huì)執(zhí)行。而 DOMContentLoaded 只有在 defer 腳本執(zhí)行結(jié)束后才會(huì)被觸發(fā)。
  • 情況1:HTML還沒(méi)解析完成時(shí),defer腳本已經(jīng)加載完畢,那么defer腳本將等待HTML解析完成后再執(zhí)行。defer腳本執(zhí)行完畢后觸發(fā)DOMContentLoaded事件
  • 情況2:HTML解析完成時(shí),defer腳本還沒(méi)加載完畢,那么defer腳本繼續(xù)加載,加載完成后直接執(zhí)行,執(zhí)行完畢后觸發(fā)DOMContentLoaded事件
8. 為什么CSS動(dòng)畫比JavaScript高效

我覺(jué)得這個(gè)題目說(shuō)法上可能就是行不通,不能這么說(shuō),如果了解的話,都知道will-change只是一個(gè)優(yōu)化的手段,使用JS改變transform也可以享受這個(gè)屬性帶來(lái)的變化,所以這個(gè)說(shuō)法上有點(diǎn)不妥。

所以圍繞這個(gè)問(wèn)題展開話,更應(yīng)該說(shuō)建議推薦使用CSS動(dòng)畫,至于為什么呢,涉及的知識(shí)點(diǎn)大概就是重排重繪,合成,這方面的點(diǎn),我在瀏覽器渲染流程中也提及了。

盡可能的避免重排和重繪,具體是哪些操作呢,如果非要去操作JS實(shí)現(xiàn)動(dòng)畫的話,有哪些優(yōu)化的手段呢?

比如??

  • 使用createDocumentFragment進(jìn)行批量的 DOM 操作
  • 對(duì)于 resize、scroll 等進(jìn)行防抖/節(jié)流處理。
  • rAF優(yōu)化等等
剩下的東西就留給你們思考吧,希望我這是拋磚引玉吧(●’?’●)
9. 能不能實(shí)現(xiàn)事件防抖和節(jié)流

函數(shù)節(jié)流(throttle)

節(jié)流的意思是讓函數(shù)有節(jié)制地執(zhí)行,而不是毫無(wú)節(jié)制的觸發(fā)一次就執(zhí)行一次。什么叫有節(jié)制呢?就是在一段時(shí)間內(nèi),只執(zhí)行一次。

規(guī)定在一個(gè)單位時(shí)間內(nèi),只能觸發(fā)一次函數(shù)。如果這個(gè)單位時(shí)間內(nèi)觸發(fā)多次函數(shù),只有一次生效。

抓取一個(gè)關(guān)鍵的點(diǎn):就是執(zhí)行的時(shí)機(jī)。要做到控制執(zhí)行的時(shí)機(jī),我們可以通過(guò)「一個(gè)開關(guān)」,與定時(shí)器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ù)制代碼

函數(shù)防抖(debounce)

在事件被觸發(fā)n秒后再執(zhí)行回調(diào),如果在這n秒內(nèi)又被觸發(fā),則重新計(jì)時(shí)。

核心思想:每次事件觸發(fā)都會(huì)刪除原有定時(shí)器,建立新的定時(shí)器。通俗意思就是反復(fù)觸發(fā)函數(shù),只認(rèn)最后一次,從最后一次開始計(jì)時(shí)。

代碼:

?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 以及常見的坑

自己造一個(gè) debounce / throttle 的輪子看起來(lái)多么誘人,或者隨便找個(gè)博文復(fù)制過(guò)來(lái)。「我是建議直接使用 underscore 或 Lodash」?。如果僅需要?_.debounce?和?_.throttle?方法,可以使用 Lodash 的自定義構(gòu)建工具,生成一個(gè) 2KB 的壓縮庫(kù)。使用以下的簡(jiǎn)單命令即可:
npm i -g lodash-cli
npm i -g lodash-clilodash-cli include=debounce,throttle復(fù)制代碼
常見的坑是,不止一次地調(diào)用?_.debounce?方法:
// 錯(cuò)誤$(window).on('scroll', function() {
_.debounce(doSomething, 300);
});// 正確$(window).on('scroll', _.debounce(doSomething, 200));復(fù)制代碼
debounce 方法保存到一個(gè)變量以后,就可以用它的私有方法?debounced_version.cancel(),lodash 和 underscore.js 都有效。
let debounced_version = _.debounce(doSomething, 200);$(window).on(‘scroll’, debounced_version);// 如果需要的話debounced_version.cancel();復(fù)制代碼

適合應(yīng)用場(chǎng)景

防抖

  • search搜索,用戶不斷輸入值時(shí),用防抖來(lái)節(jié)約Ajax請(qǐng)求,也就是輸入框事件。
  • window觸發(fā)resize時(shí),不斷的調(diào)整瀏覽器窗口大小會(huì)不斷的觸發(fā)這個(gè)事件,用防抖來(lái)讓其只觸發(fā)一次

節(jié)流

  • 鼠標(biāo)的點(diǎn)擊事件,比如mousedown只觸發(fā)一次
  • 監(jiān)聽滾動(dòng)事件,比如是否滑到底部自動(dòng)加載更多,用throttle判斷
  • 比如游戲中發(fā)射子彈的頻率(1秒發(fā)射一顆)
10. 談一談你對(duì)requestAnimationFrame(rAF)理解

正好跟節(jié)流有點(diǎn)關(guān)系,有點(diǎn)相似處,就準(zhǔn)備梳理一下這個(gè)知識(shí)點(diǎn)。

「高性能動(dòng)畫是什么,那它衡量的標(biāo)準(zhǔn)是什么呢?」

動(dòng)畫幀率可以作為衡量標(biāo)準(zhǔn),一般來(lái)說(shuō)畫面在 60fps 的幀率下效果比較好。

換算一下就是,每一幀要在 16.7ms (16.7 = 1000/60) 內(nèi)完成渲染。

我們來(lái)看看MDN對(duì)它的解釋吧??

window.requestAnimationFrame() 方法告訴瀏覽器您希望執(zhí)行動(dòng)畫并請(qǐng)求瀏覽器在下一次重繪之前調(diào)用指定的函數(shù)來(lái)更新動(dòng)畫。該方法使用一個(gè)回調(diào)函數(shù)作為參數(shù),這個(gè)回調(diào)函數(shù)會(huì)在瀏覽器重繪之前調(diào)用。— MDN

當(dāng)我們調(diào)用這個(gè)函數(shù)的時(shí)候,我們告訴它需要做兩件事:

  1. 我們需要新的一幀;
  2. 當(dāng)你渲染新的一幀時(shí)需要執(zhí)行我傳給你的回調(diào)函數(shù)

rAF與 setTimeout 相比

rAF(requestAnimationFrame) 最大的優(yōu)勢(shì)是「由系統(tǒng)來(lái)決定回調(diào)函數(shù)的執(zhí)行時(shí)機(jī)」。

具體一點(diǎn)講就是,系統(tǒng)每次繪制之前會(huì)主動(dòng)調(diào)用 rAF 中的回調(diào)函數(shù),如果系統(tǒng)繪制率是 60Hz,那么回調(diào)函數(shù)就每16.7ms 被執(zhí)行一次,如果繪制頻率是75Hz,那么這個(gè)間隔時(shí)間就變成了 1000/75=13.3ms。

換句話說(shuō)就是,rAF 的執(zhí)行步伐跟著系統(tǒng)的繪制頻率走。它能保證回調(diào)函數(shù)在屏幕每一次的繪制間隔中只被執(zhí)行一次(上一個(gè)知識(shí)點(diǎn)剛剛梳理完「函數(shù)節(jié)流」),這樣就不會(huì)引起丟幀現(xiàn)象,也不會(huì)導(dǎo)致動(dòng)畫出現(xiàn)卡頓的問(wèn)題。

另外它可以自動(dòng)調(diào)節(jié)頻率。如果callback工作太多無(wú)法在一幀內(nèi)完成會(huì)自動(dòng)降低為30fps。雖然降低了,但總比掉幀好。

與setTimeout動(dòng)畫對(duì)比的話,有以下幾點(diǎn)優(yōu)勢(shì)

  • 當(dāng)頁(yè)面隱藏或者最小化時(shí),setTimeout仍然在后臺(tái)執(zhí)行動(dòng)畫,此時(shí)頁(yè)面不可見或者是不可用狀態(tài),動(dòng)畫刷新沒(méi)有意義,而且浪費(fèi)CPU。
  • rAF不一樣,當(dāng)頁(yè)面處理未激活的狀態(tài)時(shí),該頁(yè)面的屏幕繪制任務(wù)也會(huì)被系統(tǒng)暫停,因此跟著系統(tǒng)步伐走的rAF也會(huì)停止渲染,當(dāng)頁(yè)面被激活時(shí),動(dòng)畫就從上次停留的地方繼續(xù)執(zhí)行,有效節(jié)省了 CPU 開銷。

什么時(shí)候調(diào)用呢

規(guī)范中似乎是這么去定義的:

  • 在重新渲染前調(diào)用。
  • 很可能在宏任務(wù)之后不去調(diào)用

這樣子分析的話,似乎很合理嘛,為什么要在重新渲染前去調(diào)用呢?因?yàn)閞AF作為官方推薦的一種做流暢動(dòng)畫所應(yīng)該使用的API,做動(dòng)畫不可避免的去操作DOM,而如果是在渲染后去修改DOM的話,那就只能等到下一輪渲染機(jī)會(huì)的時(shí)候才能去繪制出來(lái)了,這樣子似乎不合理。

rAF在瀏覽器決定渲染之前給你最后一個(gè)機(jī)會(huì)去改變 DOM 屬性,然后很快在接下來(lái)的繪制中幫你呈現(xiàn)出來(lái),所以這是做流暢動(dòng)畫的不二選擇。

至于宏任務(wù),微任務(wù),這可以說(shuō)起來(lái)就要展開篇幅了,暫時(shí)不在這里梳理了。

rAF與節(jié)流相比

跟?_.throttle(dosomething, 16)?等價(jià)。它是高保真的,如果追求更好的精確度的話,可以用瀏覽器原生的 API 。

可以使用 rAF API 替換 throttle 方法,考慮一下優(yōu)缺點(diǎn):

優(yōu)點(diǎn)

  • 動(dòng)畫保持 60fps(每一幀 16 ms),瀏覽器內(nèi)部決定渲染的最佳時(shí)機(jī)
  • 簡(jiǎn)潔標(biāo)準(zhǔn)的 API,后期維護(hù)成本低

缺點(diǎn)

  • 動(dòng)畫的開始/取消需要開發(fā)者自己控制,不像 ‘.debounce’ 或 ‘.throttle’由函數(shù)內(nèi)部處理。
  • 瀏覽器標(biāo)簽未激活時(shí),一切都不會(huì)執(zhí)行。
  • 盡管所有的現(xiàn)代瀏覽器都支持 rAF ,IE9,Opera Mini 和 老的 Android 還是需要打補(bǔ)丁。
  • Node.js 不支持,無(wú)法在服務(wù)器端用于文件系統(tǒng)事件。

根據(jù)經(jīng)驗(yàn),如果 JavaScript 方法需要繪制或者直接改變屬性,我會(huì)選擇?requestAnimationFrame,只要涉及到重新計(jì)算元素位置,就可以使用它。

涉及到 AJAX 請(qǐng)求,添加/移除 class (可以觸發(fā) CSS 動(dòng)畫),我會(huì)選擇?_.debounce?或者?_.throttle?,可以設(shè)置更低的執(zhí)行頻率(例子中的200ms 換成16ms)。

云和數(shù)據(jù)HTML5全棧精英班,經(jīng)過(guò)多年的技術(shù)迭代和項(xiàng)目革新,逐步發(fā)展成為集網(wǎng)站、手機(jī)應(yīng)用、小程序、快應(yīng)用、桌面應(yīng)用、后臺(tái)開發(fā)等多領(lǐng)域開發(fā)課程,新增Egg、TypeScript、Vue、React、HybridAPP等時(shí)下最流行的新技術(shù),結(jié)合企業(yè)實(shí)際用人需求,只為培養(yǎng)更多高端IT技術(shù)人才。