前言
事件在前端扮演了非常重要的角色,在瀏覽器端,它承擔了用戶交互的很大一部分工作,在服務器端,它充當了一個任務調度者的角色,使得js在單線程的環(huán)境下毫不遜色其他語言。
類型
- 瀏覽器事件
1.瀏覽器事件是如何發(fā)生的?
答:有兩種主要方式發(fā)生:冒泡和捕獲。
1.1 若事件是從根節(jié)點開始,逐級派送到子節(jié)點,自上而下的過程,這個階段稱為“捕獲階段(Capture)”;
1.2 若事件是由子節(jié)點往根節(jié)點派送,自下而上的過程,這個階段稱為“冒泡階段(Bubble)”。
1.3 一般瀏覽器是先捕獲再處理再冒泡。
1.4 可以手動設置一個事件的發(fā)生方式是冒泡還是捕獲模式,IE不支持設置捕獲模式。
2.事件委托是怎么回事?
答:考慮性能的情況下,父元素代理子元素的所有事件,根據(jù)不同子元素觸發(fā)不同的邏輯。
舉個例子:一個ui元素下有一萬個li元素,總不能每個li元素都綁定一個事件吧,把觸發(fā)邏輯的控制權交給ui元素,那就輕松多了。
<body>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
<script>
const ul = document.querySelector("ul")
ul.addEventListener("click", delegate)
function delegate(event) {
console.dir(event.target)
switch(event.target.textContent){
case "1":
alert("第一個li元素")
break
case "2":
alert("第二個li元素")
break
case "3":
alert("第三個li元素")
break
default:
alert("其他li元素")
}
}
</script>
</body>
- node事件
1.為什么說Node是事件驅動的?
答:因為Node的API大部分都建立在回調基礎上,而這個回調函數(shù)就是Node事件驅動的具體表現(xiàn)。舉個例子:fs.readFile函數(shù)的參數(shù)中必須有一個是回調函數(shù),這個函數(shù)在讀文件的時候,操作系統(tǒng)會類似瀏覽器中監(jiān)聽到了一個“讀文件”事件,然后調用相應的回調。
2.事件循環(huán)是怎么回事?
答:Node官網(wǎng)提供了一副圖來解釋:
┌───────────────────────┐
┌─>│ timers │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ I/O callbacks │
│ └──────────┬────────────┘
│ ┌──────────┴────────────┐
│ │ idle, prepare │
│ └──────────┬────────────┘ ┌───────────────┐
│ ┌──────────┴────────────┐ │ incoming: │
│ │ poll │<─────┤ connections, │
│ └──────────┬────────────┘ │ data, etc. │
│ ┌──────────┴────────────┐ └───────────────
│ │ check │
│ └──────────┬────────────
│ ┌──────────┴────────────┐
└──┤ close callbacks │
└───────────────────────┘
由于JS是單線程的,Node巧妙地把原本多線程的任務轉移給操作系統(tǒng)去做了,簡而言之就是Node將適當?shù)幕卣{添加到輪詢隊列中去,然后經(jīng)過定時器、I/O回調、poll(調查)、check(檢查)、關閉回調循環(huán)執(zhí)行任務,故這個過程又叫事件循環(huán)。它是永遠不會阻塞的。
詳情可以參考阮一峰先生的JavaScript 運行機制詳解:再談Event Loop
- 自定義事件
1.瀏覽器自定義事件
使用Event()構造
var event = new Event('build');
// 監(jiān)聽事件
elem.addEventListener('build', function (e) { ... }, false);
// 觸發(fā)事件
elem.dispatchEvent(event);
2.Node端自定義事件
使用EventEmiter模塊
const EventEmitter = require('events');
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter();
myEmitter.on('event', () => {
console.log('an event occurred!');
});
myEmitter.emit('event');
使用
1.監(jiān)聽和取消監(jiān)聽事件
// js文件中
elem.addEventListener('click', function(){...});
// HTML文件中
<button onclick="func()">
//老式js寫法
elem.onclick = function(event){}};
2.阻止事件
寫在事件觸發(fā)的回調函數(shù)里
//取消事件但不阻止傳播
event.preventDefault();
//阻止事件傳播
event.stopPropagation();
3.常見事件種類
3.1 鍵鼠事件
key-mouse
keydown:按下任意按鍵
keypress:除 Shift, Fn, CapsLock 外任意鍵被按住. (連續(xù)觸發(fā))
keyup:釋放任意按鍵
mousedown:在元素上按下任意鼠標按鈕
mouseup:在元素上釋放任意鼠標按鍵
click:在元素上按下并釋放任意鼠標按鍵
dblclick:在元素上雙擊鼠標按鈕
contextmenu:右鍵點擊 (右鍵菜單顯示前).
wheel:滾輪向任意方向滾動
mouseenter:指針移到有事件監(jiān)聽的元素內
mouseover:指針移到有事件監(jiān)聽的元素或者它的子元素內
mousemove:指針在元素內移動時持續(xù)觸發(fā)
mouseleave:指針移出元素范圍外(不冒泡)
mouseout:指針移出元素,或者移到它的子元素上
3.2 拖拽事件
drag
dragenter:被拖動的元素或文本選區(qū)移入有效釋放目標區(qū)
dragstart:用戶開始拖動HTML元素或選中的文本
dragend:拖放操作結束 (松開鼠標按鈕或按下Esc鍵)
dragover:被拖動的元素或文本選區(qū)正在有效釋放目標上被拖動 (在此過程中持續(xù)觸發(fā),每350ms觸發(fā)一次)
dragleave:被拖動的元素或文本選區(qū)移出有效釋放目標區(qū)
drag:正在拖動元素或文本選區(qū)(在此過程中持續(xù)觸發(fā),每350ms觸發(fā)一次)
drop:元素在有效釋放目標區(qū)上釋放
3.3 node事件
文件模塊的讀寫相關事件、HTTP模塊相關事件等等
3.4 其他事件
如資源事件(load/error等)、網(wǎng)絡事件(online)、焦點事件(focus)、會話歷史事件(pageshow/popstate)、
CSS 動畫事件(animationstart)、表單事件(submit)、視圖事件(resize/scroll)、媒體事件(play/pause)、進度事件(load)、值變化事件(input/ValueChange)等等
事件的未來
當多個事件出現(xiàn)在代碼里的時候,邏輯變得不是那么清晰,這個時候把事件轉化成流來處理就非常方便了,具體可以參考RXJS對事件的處理。
總結
編寫前端代碼,離不開處理事件,原生js提供了我們監(jiān)聽事件并執(zhí)行回調函數(shù)的功能(包括node),主流框架也都封裝了事件的相關API,用來實現(xiàn)更強大的功能,比如vue和NG用事件來傳遞子->父組件的數(shù)據(jù),總之,對事件的理解以及如何去利用事件完成需求,我認為,是一個前端工程師的核心技能之一。