我想你很可能聽說過事件驅動, 但是事件驅動到底是什麼?為什麼說瀏覽器是事件驅動的呢?為什麼 nodejs 也是事件驅動的 ? 兩者是一回事麼?
實際上不管是瀏覽器還是 nodejs 都是事件驅動的,都有自己的事件模型。在這裡,我們只講解瀏覽器端的事件模型,如果對 nodejs 事件模型感興趣的,請期待我的 nodejs 部分的講解。
事件驅動通俗地來說就是什麼都抽象為事件。
本篇文章不講解事件迴圈的內容,事件迴圈部分會在本章的其他章節講解,敬請期待。
其實現實中的紅綠燈就是一種事件,它告訴我們現在是紅燈狀態,綠燈狀態,還是黃燈狀態。 我們需要根據這個事件自己去完成一些操作,比如紅燈和黃燈我們需要等待,綠燈我們可以過馬路。
下面我們來看乙個最簡單的瀏覽器端的事件:
html**:
change colorbutton>
js**:
var btn = document.queryselector('button');
btn.onclick = function()
**很簡單,我們在button上註冊了乙個事件,這個事件的handler是乙個我們定義的匿名函式。當使用者點選了這個被註冊了事件的button的時候,這個我們定義好的匿名函式就會被執行。
我們有三種方法可以繫結事件,分別是行內繫結,直接賦值,用addeventlistener。
這個方法非常不推薦html**:
press mebutton>
然後在script標籤內寫:
function handleclick()
和我上面舉的例子一樣:
var btn = document.queryselector('button');
btn.onclick = function()
這種方法有兩個缺點
不能新增多個同型別的handler
btn.onclick = functiona;
btn.onclick = functionb;
這樣只有functionb有效,這可以通過addeventlistener來解決。
不能控制在哪個階段來執行,這個會在後面將事件捕獲/冒泡的時候講到。這個同樣可以通過addeventlistener來解決。
因此addeventlistener橫空出世,這個也是目前推薦的寫法。
舊版本的addeventlistener第三個引數是bool,新版版的第三個引數是物件,這樣方便之後的擴充套件,承載更多的功能, 我們來重點介紹一下它。
addeventlistener可以給element,document,window,甚至xmlhttprequest等繫結事件,當指定的事件發生的時候,繫結的**函式就會被以某種機制進行執行,這種機制我們稍後就會講到。
語法:
target.addeventlistener(type, listener[, options]);
target.addeventlistener(type, listener[, usecapture]);
target.addeventlistener(type, listener[, usecapture, wantsuntrusted ]); // gecko/mozilla only
type是你想要繫結的事件型別,常見的有click, scroll, touch, mouseover等,舊版本的第三個引數是bool,表示是否是捕獲階段,預設是false,即預設為冒泡階段。新版本是乙個物件,其中有capture(和上面功能一樣),passive和once。 once用來執行是否只執行一次,passive如果被指定為true表示永遠不會執行preventdefault(),這在實現絲滑柔順的滾動的效果中很重要。更多請參考improving scrolling performance with passive listeners
雖然我們很少時候會接觸到原生的事件,但是了解一下事件物件,事件機制,事件**等還是很有必要的,因為框架的事件系統至少在這方面還是一致的,這些內容我們接下來就會講到。
function handleclick(e)
btn.addeventlistener('click', handleclick);
這個e就是事件物件,即event object。 這個物件有一些很有用的屬性和方法,下面舉幾個常用的屬性和方法。
...
前面講到了事件預設是繫結到冒泡階段的,如果你顯式令usecapture為true,則會繫結到捕獲階段。
事件捕獲很有意思,以至於我會經常出事件的題目加上一點事件傳播的機制,讓候選人進行回答,這很能體現乙個人的水平。了解事件的傳播機制,對於一些特定問題有著非常大的作用。
乙個element上繫結的事件觸發了,那麼其實會經過三個階段。
從最外層即html標籤開始,檢查當前元素有沒有繫結對應捕獲階段事件,如果有則執行,沒有則繼續往裡面傳播,這個過程遞迴執行直到觸達觸發這個事件的元素為止。
偽**:
function capture(e, currentelement)
// pass down
if (currentelement !== e.target) else
}// 這個event物件由引擎建立
capture(new event(), document.queryselector('html'))
上面已經提到了,這裡省略了。
從觸發這個事件的元素開始,檢查當前元素有沒有繫結對應冒泡階段事件,如果有則執行,沒有則繼續往裡面傳播,這個過程遞迴執行直到觸達html為止。
偽**:
function bubble(e, currentelement)
// returning
if (currentelement !== document.queryselector('html'))
}
上述的過程用圖來表示為:
如果你不希望事件繼續冒泡,可以用之前我提到的stoppropagation。
偽**:
function bubble(e, currentelement)
if (currentelement.listners[e.type] !== void 0) );
if (stopped) return;})}
// returning
if (currentelement !== document.queryselector('html'))
}
利用上面提到的事件冒泡機制,我們可以選擇做一些有趣的東西。 舉個例子:
我們有乙個如下的列表,我們想在點選對應列表項的時候,輸出是點選了哪個元素。
html**:
1li>
2li>
3li>
4li>
ul>
js**:
document.queryselector('ul').addeventlistener('click', e => console.log(e.target.innerhtml))
「事件會從目標階段開始」,並不是說事件沒有捕獲階段,而是我們沒有繫結捕獲階段,我描述給省略了。我們只給外層的ul繫結了事件處理函式,但是可以看到li點選的時候,實際上會列印出對應li的內容(1,2,3或者4)。 我們無須給每乙個li繫結事件處理函式,不僅從**量還是效能上都有一定程度的提公升。
這個有趣的東西,我們給了它乙個好聽的名字「事件**」。在實際業務中我們會經常使用到這個技巧,這同時也是面試的高頻考點。
廣州品牌設計公司
事件其實不是瀏覽器特有的,和js語言也沒有什麼關係,這也是我為什麼沒有將其劃分到js部分的原因。很多地方都有事件系統,但是各種事件模型又不太一致。
我們今天講的是瀏覽器的事件模型,瀏覽器基於事件驅動,將很多東西都抽象為事件,比如使用者互動,網路請求,頁面載入,報錯等,可以說事件是瀏覽器正常執行的基石。
我們在使用的框架都對事件進行了不同程度的封裝和處理,除了了解原生的事件和原理,有時候了解一下框架本身對事件的處理也是很有必要的。
當發生乙個事件的時候,瀏覽器會初始化乙個事件物件,然後將這個事件物件按照一定的邏輯進行傳播,這個邏輯就是事件傳播機制。 我們提到了事件傳播其實分為三個階段,按照時間先後順序分為捕獲階段,目標階段和冒泡階段。開發者可以選擇監聽不同的階段,從而達到自己想要的效果。
事件物件有很多屬性和方法,允許你在事件處理函式中進行讀取和操作,比如讀取點選的座標資訊,阻止冒泡等。
最後我們通過乙個例子,說明了如何利用冒泡機制來實現事件**。
瀏覽器事件
常用瀏覽器事件與dom事件,包括滑鼠事件 鍵盤事件 框架 物件事件 表單事件 剪貼簿事件 列印事件 拖動事件 多 事件 動畫事件 過渡事件。onbeforeinstallprompt 當使用者即將被提示安裝web應用程式時,該處理程式將在裝置上排程,其相關聯的事件可以儲存以供稍後用於在更適合的時間提...
瀏覽器事件
常用瀏覽器事件與dom事件,包括滑鼠事件 鍵盤事件 框架 物件事件 表單事件 剪貼簿事件 列印事件 拖動事件 多 事件 動畫事件 過渡事件。onbeforeinstallprompt 當使用者即將被提示安裝web應用程式時,該處理程式將在裝置上排程,其相關聯的事件可以儲存以供稍後用於在更適合的時間提...
瀏覽器事件機制
事件被觸發三階段 1.document往事件觸發處傳播,會觸發遇到註冊的捕獲事件 2.傳播到事件觸發處,觸發註冊事件 3.從事件觸發處往document傳播,遇到註冊的冒泡事件,會觸發。事件觸發機制一般會按上面的順序觸發,但也有特例,如果給乙個目標節點同時註冊冒泡事件和捕獲事件,事件觸發會按註冊的順...