1 事件機(jī)制
事件觸發(fā)三階段
-
document 往事件觸發(fā)處傳播,遇到注冊(cè)的捕獲事件會(huì)觸發(fā)
-
傳播到事件觸發(fā)處時(shí)觸發(fā)注冊(cè)的事件
-
從事件觸發(fā)處往 document 傳播,遇到注冊(cè)的冒泡事件會(huì)觸發(fā)
事件觸發(fā)?般來(lái)說(shuō)會(huì)按照上?的順序進(jìn)?,但是也有特例,如果給?個(gè)?標(biāo)節(jié)點(diǎn)同時(shí)注 冊(cè)冒泡和捕獲事件,事件觸發(fā)會(huì)按照注冊(cè)的順序執(zhí)?
// 以下會(huì)先打印冒泡然后是捕獲
node.addEventListener('click',(event) =>{
console.log('冒泡')
},false);
node.addEventListener('click',(event) =>{
console.log('捕獲 ')
},true)
注冊(cè)事件
- 通常我們使? addEventListener 注冊(cè)事件,該函數(shù)的第三個(gè)參數(shù)可以是布爾值,也可以是對(duì)象。對(duì)于布爾值 useCapture 參數(shù)來(lái)說(shuō),該參數(shù)默認(rèn)值為 false 。useCapture 決定了注冊(cè)的事件是捕獲事件還是冒泡事件
- ?般來(lái)說(shuō),我們只希望事件只觸發(fā)在?標(biāo)上,這時(shí)候可以使? stopPropagation 來(lái)阻?事件的進(jìn)?步傳播。通常我們認(rèn)為 stopPropagation 是?來(lái)阻?事件冒泡的,其實(shí)該函數(shù)也可以阻?捕獲事件。 stopImmediatePropagation 同樣也能實(shí)現(xiàn)阻?事件,但是還能阻?該事件?標(biāo)執(zhí)?別的注冊(cè)事件
node.addEventListener('click',(event) =>{
event.stopImmediatePropagation()
console.log('冒泡')
},false);
// 點(diǎn)擊 node 只會(huì)執(zhí)?上?的函數(shù),該函數(shù)不會(huì)執(zhí)?
node.addEventListener('click',(event) => {
console.log('捕獲 ')
},true)
事件代理
如果?個(gè)節(jié)點(diǎn)中的?節(jié)點(diǎn)是動(dòng)態(tài)?成的,那么?節(jié)點(diǎn)需要注冊(cè)事件的話應(yīng)該注冊(cè)在?節(jié)點(diǎn)上
<ul id="ul">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
<script>
let ul = document.querySelector('##ul')
ul.addEventListener('click', (event) => {
console.log(event.target);
})
</script>
事件代理的?式相對(duì)于直接給?標(biāo)注冊(cè)事件來(lái)說(shuō),有以下優(yōu)點(diǎn)
- 節(jié)省內(nèi)存
- 不需要給?節(jié)點(diǎn)注銷(xiāo)事件
2 跨域
因?yàn)闉g覽器出于安全考慮,有同源策略。也就是說(shuō),如果協(xié)議、域名或者端?有?個(gè)不同就是跨域, Ajax 請(qǐng)求會(huì)失敗
JSONP
JSONP 的原理很簡(jiǎn)單,就是利? <script> 標(biāo)簽沒(méi)有跨域限制的漏洞。通過(guò) <script> 標(biāo)簽指向?個(gè)需要訪問(wèn)的地址并提供?個(gè)回
調(diào)函數(shù)來(lái)接收數(shù)據(jù)當(dāng)需要通訊時(shí)
<script src="http://domain/api?param1=a¶m2=b&callback=jsonp"></script>
<script>
function jsonp(data) {
console.log(data)
}
</script>
- JSONP 使?簡(jiǎn)單且兼容性不錯(cuò),但是只限于 get 請(qǐng)求
CORS
- CORS 需要瀏覽器和后端同時(shí)?持
- 瀏覽器會(huì)?動(dòng)進(jìn)? CORS 通信,實(shí)現(xiàn) CORS 通信的關(guān)鍵是后端。只要后端實(shí)現(xiàn)了CORS ,就實(shí)現(xiàn)了跨域。
- 服務(wù)端設(shè)置 Access-Control-Allow-Origin 就可以開(kāi)啟 CORS 。 該屬性表示哪些域名可以訪問(wèn)資源,如果設(shè)置通配符則表示所有?站都可以訪問(wèn)資源
document.domain
-
該?式只能?于?級(jí)域名相同的情況下,?如 a.test.com 和 b.test.com 適?于該?式。
-
只需要給??添加 document.domain = ‘test.com’ 表示?級(jí)域名都相同就可以實(shí)現(xiàn)跨域
postMessage
這種?式通常?于獲取嵌???中的第三???數(shù)據(jù)。?個(gè)??發(fā)送消息,另?個(gè)??判斷來(lái)源并接收消息
// 發(fā)送消息端
window.parent.postMessage('message', 'http://blog.poetries.com');
// 接收消息端
var mc = new MessageChannel();
mc.addEventListener('message', (event) => {
var origin = event.origin || event.originalEvent.origin;
if (origin === 'http://blog.poetries.com') {
console.log('驗(yàn)證通過(guò)')
}
});
3 Event loop
JS中的event loop
眾所周知 JS 是??阻塞單線程語(yǔ)?,因?yàn)樵谧畛?JS 就是為了和瀏覽器交互?誕?的。如果 JS 是?多線程的語(yǔ)?話,我們?cè)诙鄠€(gè)
線程中處理 DOM就可能會(huì)發(fā)?問(wèn)題(?個(gè)線程中新加節(jié)點(diǎn),另?個(gè)線程中刪除節(jié)點(diǎn))
- JS 在執(zhí)?的過(guò)程中會(huì)產(chǎn)?執(zhí)?環(huán)境,這些執(zhí)?環(huán)境會(huì)被順序的加?到執(zhí)?棧中。如果遇到異步的代碼,會(huì)被掛起并加?到 Task (有多種 task ) 隊(duì)列中。?旦執(zhí)?棧為空,Event Loop 就會(huì)從 Task 隊(duì)列中拿出需要執(zhí)?的代碼并放?執(zhí)?棧中執(zhí)?,所以本質(zhì)上來(lái)說(shuō) JS 中的異步還是同步?為
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
console.log('script end');
- 不同的任務(wù)源會(huì)被分配到不同的 Task 隊(duì)列中,任務(wù)源可以分為 微任務(wù)( microtask ) 和 宏任務(wù)( macrotask )。在 ES6 規(guī)范中,microtask 稱為 jobs,macrotask 稱為 task
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
new Promise((resolve) => {
console.log('Promise')
resolve()
}).then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('script end');
// script start => Promise => script end => promise1 => promise2 => setTime
以上代碼雖然 setTimeout 寫(xiě)在 Promise 之前,但是因?yàn)?Promise 屬于微任務(wù)? setTimeout 屬于宏任務(wù)
微任務(wù)
- process.nextTick
- promise
- Object.observe
- MutationObserver
宏任務(wù)
-
script
-
setTimeout
-
setInterval
-
setImmediate
-
I/O
-
UI rendering
宏任務(wù)中包括了 script ,瀏覽器會(huì)先執(zhí)??個(gè)宏任務(wù),接下來(lái)有異步代碼
的話就先執(zhí)?微任務(wù)
所以正確的?次 Event loop 順序是這樣的
-
執(zhí)?同步代碼,這屬于宏任務(wù)
-
執(zhí)?棧為空,查詢是否有微任務(wù)需要執(zhí)?
-
執(zhí)?所有微任務(wù)
-
必要的話渲染 UI
-
然后開(kāi)始下?輪 Event loop ,執(zhí)?宏任務(wù)中的異步代碼
通過(guò)上述的 Event loop 順序可知,如果宏任務(wù)中的異步代碼有?量的計(jì)算并且需要操作 DOM 的話,為了更快的響應(yīng)界?響 應(yīng),我們可以把操作 DOM放?微任務(wù)中
Node 中的 Event loop
- Node 中的 Event loop 和瀏覽器中的不相同。
- Node 的 Event loop 分為 6 個(gè)階段,它們會(huì)按照順序反復(fù)運(yùn)?
┌───────────────────────┐
┌─>│ timers │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ I/O callbacks │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ idle, prepare │
│ └──────────┬────────────┘ ┌───────────────┐
│ ┌──────────┴────────────┐ │ incoming: │
│ │ poll │<──connections─── │
│ └──────────┬────────────┘ │ data, etc. │
│ ┌──────────┴────────────┐ └───────────────┘
│ │ check │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
└──┤ close callbacks │
└───────────────────────┘
timer
- timers 階段會(huì)執(zhí)? setTimeout 和 setInterval
- ?個(gè) timer 指定的時(shí)間并不是準(zhǔn)確時(shí)間,?是在達(dá)到這個(gè)時(shí)間后盡快執(zhí)?回調(diào),可能會(huì)因?yàn)橄到y(tǒng)正在執(zhí)?別的事務(wù)?延遲
I/O
- I/O 階段會(huì)執(zhí)?除了 close 事件,定時(shí)器和 setImmediate 的回調(diào)
poll
- poll 階段很重要,這?階段中,系統(tǒng)會(huì)做兩件事情
- 執(zhí)?到點(diǎn)的定時(shí)器
- 執(zhí)? poll 隊(duì)列中的事件
- 并且當(dāng) poll 中沒(méi)有定時(shí)器的情況下,會(huì)發(fā)現(xiàn)以下兩件事情
- 如果 poll 隊(duì)列不為空,會(huì)遍歷回調(diào)隊(duì)列并同步執(zhí)?,直到隊(duì)列為空或者系統(tǒng)限制
- 如果 poll 隊(duì)列為空,會(huì)有兩件事發(fā)?
- 如果有 setImmediate 需要執(zhí)?, poll 階段會(huì)停?并且進(jìn)?到 check 階段執(zhí)?setImmediate
- 如果沒(méi)有 setImmediate 需要執(zhí)?,會(huì)等待回調(diào)被加?到隊(duì)列中并?即執(zhí)?回調(diào)
- 如果有別的定時(shí)器需要被執(zhí)?,會(huì)回到 timer 階段執(zhí)?回調(diào)。
check
- check 階段執(zhí)? setImmediate
close callbacks
- close callbacks 階段執(zhí)? close 事件
- 并且在 Node 中,有些情況下的定時(shí)器執(zhí)?順序是隨機(jī)的
setTimeout(() => {
console.log('setTimeout');
}, 0);
setImmediate(() => {
console.log('setImmediate');
})
// 這?可能會(huì)輸出 setTimeout,setImmediate
// 可能也會(huì)相反的輸出,這取決于性能
// 因?yàn)榭赡苓M(jìn)? event loop ?了不到 1 毫秒,這時(shí)候會(huì)執(zhí)? setImmediate
// 否則會(huì)執(zhí)? setTimeout
上?介紹的都是 macrotask 的執(zhí)?情況, microtask 會(huì)在以上每個(gè)階段完成后?即執(zhí)?
setTimeout(()=>{
console.log('timer1')
Promise.resolve().then(function() {
console.log('promise1')
})
}, 0)
setTimeout(()=>{
console.log('timer2')
Promise.resolve().then(function() {
console.log('promise2')
})
}, 0)
// 以上代碼在瀏覽器和 node 中打印情況是不同的
// 瀏覽器中?定打印 timer1, promise1, timer2, promise2
// node 中可能打印 timer1, timer2, promise1, promise2
// 也可能打印 timer1, promise1, timer2, promise2
Node 中的 process.nextTick 會(huì)先于其他 microtask 執(zhí)?
setTimeout(() => {
console.log("timer1");
Promise.resolve().then(function() {
console.log("promise1");
});
}, 0);
process.nextTick(() => {
console.log("nextTick");
});
// nextTick, timer1, promise1
4 Service Worker
Service workers 本質(zhì)上充當(dāng)Web應(yīng)?程序與瀏覽器之間的代理服務(wù)器,也可以在?絡(luò)可?時(shí)作為瀏覽器和?絡(luò)間的代理。它們旨在
(除其他之外)使得能夠創(chuàng)建有效的離線體驗(yàn),攔截?絡(luò)請(qǐng)求并基于?絡(luò)是否可?以及更新的資源是否駐留在服務(wù)器上來(lái)采取適當(dāng)?shù)?動(dòng)作。他們還允許訪問(wèn)推送通知和后臺(tái)同步API
?前該技術(shù)通常?來(lái)做緩存?件,提??屏速度
// index.js
if (navigator.serviceWorker) {
navigator.serviceWorker
.register("sw.js")
.then(function(registration) {
console.log("service worker 注冊(cè)成功");
})
.catch(function(err) {
console.log("servcie worker 注冊(cè)失敗");
});
}
// sw.js
// 監(jiān)聽(tīng) `install` 事件,回調(diào)中緩存所需?件
self.addEventListener("install", e => {
e.waitUntil(
caches.open("my-cache").then(function(cache) {
return cache.addAll(["./index.html", "./index.js"]);
})
);
});
// 攔截所有請(qǐng)求事件
// 如果緩存中已經(jīng)有請(qǐng)求的數(shù)據(jù)就直接?緩存,否則去請(qǐng)求數(shù)據(jù)
self.addEventListener("fetch", e => {
e.respondWith(
caches.match(e.request).then(function(response) {
if (response) {
return response;
}
console.log("fetch source");
})
);
});
- 打開(kāi)??,可以在開(kāi)發(fā)者?具中的 Application 看到 Service Worker 已經(jīng)啟動(dòng)了
- 在 Cache 中也可以發(fā)現(xiàn)我們所需的?件已被緩存
- 當(dāng)我們重新刷新??可以發(fā)現(xiàn)我們緩存的數(shù)據(jù)是從 Service Worker 中讀取的
5 渲染機(jī)制
瀏覽器的渲染機(jī)制?般分為以下?個(gè)步驟
- 處理 HTML 并構(gòu)建 DOM 樹(shù)。
- 處理 CSS 構(gòu)建 CSSOM 樹(shù)。
- 將 DOM 與 CSSOM 合并成?個(gè)渲染樹(shù)。
- 根據(jù)渲染樹(shù)來(lái)布局,計(jì)算每個(gè)節(jié)點(diǎn)的位置。
- 調(diào)? GPU 繪制,合成圖層,顯示在屏幕上
- 在構(gòu)建 CSSOM 樹(shù)時(shí),會(huì)阻塞渲染,直? CSSOM 樹(shù)構(gòu)建完成。并且構(gòu)建 CSSOM 樹(shù)是?個(gè)?分消耗性能的過(guò)程,所以應(yīng)該盡量保證層級(jí)扁平,減少過(guò)度層疊,越是具體的 CSS 選擇器,執(zhí)?速度越慢
- 當(dāng) HTML 解析到 script 標(biāo)簽時(shí),會(huì)暫停構(gòu)建 DOM,完成后才會(huì)從暫停的地?重新開(kāi)始。也就是說(shuō),如果你想?屏渲染的越快,就越不應(yīng)該在?屏就加載 JS ?件。并且 CSS 也會(huì)影響 JS 的執(zhí)?,只有當(dāng)解析完樣式表才會(huì)執(zhí)? JS,所以也可以認(rèn)為這種情況下,CSS 也會(huì)暫停構(gòu)建 DOM
圖層
?般來(lái)說(shuō),可以把普通?檔流看成?個(gè)圖層。特定的屬性可以?成?個(gè)新的圖層。不同的圖層渲染互不影響,所以對(duì)于某些頻繁需要
渲染的建議單獨(dú)?成?個(gè)新圖層,提?性能。但也不能?成過(guò)多的圖層,會(huì)引起反作?
- 通過(guò)以下?個(gè)常?屬性可以?成新圖層
- 3D 變換: translate3d 、 translateZ
- will-change
- video 、 iframe 標(biāo)簽
- 通過(guò)動(dòng)畫(huà)實(shí)現(xiàn)的 opacity 動(dòng)畫(huà)轉(zhuǎn)換
- position: fixed
重繪(Repaint)和回流(Reflow)
-
重繪是當(dāng)節(jié)點(diǎn)需要更改外觀?不會(huì)影響布局的,?如改變 color 就叫稱為重繪
-
回流是布局或者?何屬性需要改變就稱為回流文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-611358.html
回流必定會(huì)發(fā)?重繪,重繪不?定會(huì)引發(fā)回流?;亓魉璧某杀?重繪?的多,改變深層次的節(jié)點(diǎn)很可能 導(dǎo)致?節(jié)點(diǎn)的?系列回流
-
所以以下?個(gè)動(dòng)作可能會(huì)導(dǎo)致性能問(wèn)題:文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-611358.html
- 改變 window ??
- 改變字體
- 添加或刪除樣式
- ?字改變
- 定位或者浮動(dòng)
- 盒模型
很多?不知道的是,重繪和回流其實(shí)和 Event loop 有關(guān)
- 當(dāng) Event loop 執(zhí)?完 Microtasks 后,會(huì)判斷 document 是否需要更新。因?yàn)闉g覽器是 60Hz 的刷新率,每 16ms 才會(huì)更新?次。
- 然后判斷是否有 resize 或者 scroll ,有的話會(huì)去觸發(fā)事件,所以 resize 和scroll 事件也是?少 16ms 才會(huì)觸發(fā)?次,并且?帶節(jié)流功能。
- 判斷是否觸發(fā)了 media query
- 更新動(dòng)畫(huà)并且發(fā)送事件
- 判斷是否有全屏操作事件
- 執(zhí)? requestAnimationFrame 回調(diào)
- 執(zhí)? IntersectionObserver 回調(diào),該?法?于判斷元素是否可?,可以?于懶加載上,但是兼容性不好
- 更新界?
- 以上就是?幀中可能會(huì)做的事情。如果在?幀中有空閑時(shí)間,就會(huì)去執(zhí)?requestIdleCallback 回調(diào)
減少重繪和回流
- 使? translate 替代 top
- 使? visibility 替換 display: none ,因?yàn)榍罢咧粫?huì)引起重繪,后者會(huì)引發(fā)回流(改變了布局)
- 不要使? table 布局,可能很?的?個(gè)?改動(dòng)會(huì)造成整個(gè) table 的重新布局
- 動(dòng)畫(huà)實(shí)現(xiàn)的速度的選擇,動(dòng)畫(huà)速度越快,回流次數(shù)越多,也可以選擇使?requestAnimationFrame
- CSS 選擇符從右往左匹配查找,避免 DOM 深度過(guò)深
- 將頻繁運(yùn)?的動(dòng)畫(huà)變?yōu)閳D層,圖層能夠阻?該節(jié)點(diǎn)回流影響別的元素。?如對(duì)于 video
標(biāo)簽,瀏覽器會(huì)?動(dòng)將該節(jié)點(diǎn)變?yōu)閳D層
到了這里,關(guān)于前端高級(jí)面試題-瀏覽器的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!