今天面試官問了這麼一個問題:onclick
與 addEventListener
有哪些區別呢
很好問住了,自己答得不太滿意,下來自己查了查紅寶書第 17 章事件和 MDN,大概了解了是怎麼一回事
上來先把答案擺上:
區別#
addEventListener()
是 W3C DOM 規範中提供的註冊事件監聽器的方法。它的優點包括:
- 允許給一個事件註冊多個監聽器
- 特別是在使用 AJAX 庫,JavaScript 模塊,或其他需要第三方庫 / 插件的代碼
- 提供了一種更精細的手段控制
listener
的觸發階段(可以選擇捕獲或者冒泡) - 它對 任何 DOM 元素 都是有效的,而不僅僅只對 HTML 元素有效。
- 它註冊的事件可以通過
removeEventListener
來移除- 也就是說若添加的是匿名事件函數就無法移除了
this
的值是觸發事件的元素的引用console.log(e.currentTarget === this) // true
而 onclick
是 註冊 listener
的舊方法
- 該方法會替換掉這個元素上所有已存在的 onclick 事件,其他 on 事件也是類似的。
- 無法精細控制冒泡與否等
- 移除可通過直接將 onclick 事件替換為 null
兼容性#
addEventListener
在 DOM 2 Events 規範中引入
- 在 IE 9 之前,必須使用
attachEvent
而不是使用addEventListener
attachEvent
方法有個缺點,this
的值會變成window
對象的引用而不是觸發事件的元素
而 onclick
是 DOM 0 規範的基本內容
- 幾乎所有瀏覽器都支持,而且不需要特殊的跨瀏覽器兼容代碼
- 因此通常這個方法被用於動態地註冊事件處理器,除非必須使用
addEventListener()
才能提供的特殊特性
addEventListener#
EventTarget.addEventListener() 方法將指定的監聽器註冊到
EventTarget
上,當該對象觸發指定的事件時,指定的回調函數就會被執行。 事件目標可以是一個文檔上的元素Element
,Document
和Window
或者任何其他支持事件的對象 (比如XMLHttpRequest
)。
addEventListener()
的工作原理是將實現EventListener
的函數或對象添加到調用它的EventTarget
上的指定事件類型的事件監聽器列表中。
addEventListener
中有三個參數,type
、listener
和 useCapture
,其中第一個參數為 事件類型(click
、mousemove
等,第二個參數為事件的回調函數,第三個參數為一個指定有關 listener
屬性的可選參數對象,需注意的是:
在舊版本的 DOM 的規定中,
addEventListener()
的第三個參數是一個布爾值表示是否在捕獲階段調用事件處理程序。隨著時間的推移,很明顯需要更多的選項。與其在方法之中添加更多參數(傳遞可選值將會變得異常複雜),倒不如把第三個參數改為一個包含了各種屬性的對象,這些屬性的值用來被配置刪除事件監聽器的過程。
因為舊版本的瀏覽器(以及一些相對不算古老的)仍然假定第三個參數是布爾值,你需要編寫一些代碼來有效地處理這種情況。你可以對每一個你感興趣的 options 值進行特性檢測。
也就是說,addEventListener
需要額外的代碼來兼容舊瀏覽器,而 onclick 不需要,在要考慮兼容性的場景下就需要好好考慮。
事件中的 this#
addEventListener
中 this
的值通常情況下都是觸發事件的元素的引用
- console.log(e.currentTarget === this) // true
但是箭頭函數不然,箭頭函數沒有自己的 this,箭頭函數只會從自己的作用域鏈的上一層繼承 this
onclick#
全局事件處理器(
GlobalEventHandlers
)的onclick
屬性,是處理當前元素的click
事件的事件處理器(event handler)。
當用戶點擊一個元素時,會觸發
click
事件。在每次點擊的整個過程中,click
事件的運行順序在mousedown
和mouseup
事件之後。
備註: 當你使用
click
事件去觸發一個動作時,也要考慮向keydown
事件添加此動作,以便允許不使用鼠標或觸摸屏的用戶進行同樣的操作。
MDN 上講的沒那麼詳細,紅寶書中對 onclick 的描述有很重要的幾點
拓展作用域鏈#
<script>
function showMessage() {
console.log("Hello world!");
}
</script>
<input type="button" value="Click Me" onclick="showMessage()"/>
上面以onclick
方式指定的事件處理程序會創建一個函數來封裝屬性的值。(但是不建議這麼做,原因結尾有)
這個函數有一個特殊的局部變量 event
,其中保存的就是 event
對象
<!-- 輸出"click" -->
<input type="button" value="Click Me" onclick="console.log(event.type)">
有了這個對象,就不用開發者另外定義其他變量,也不用從包裝函數的參數列表中去取了。 在這個函數中,this
值相當於事件的目標元素,如下面的例子所示:
<!-- 輸出"Click Me" -->
<input type="button" value="Click Me" onclick="console.log(this.value)">
這個動態創建的包裝函數還有一個特別有意思的地方,就是其作用域鏈被擴展了。在這個函數中, document
和元素自身的成員都可以被當成局部變量來訪問。而這是通過使用 with
實現的:
function() {
with(document) {
with(this) {
// 屬性值
}
}
}
這也是為什麼在 onclick 定義的事件,調用 document 上的事件時可以免去 document 前綴,實際上是它在前面還做了一層包裝。
使用 HTML 指定 onclick 事件處理的一個問題是使 HTML 與 JavaScript 變得強耦合,如果需要修改事件處理程序,則必須在 HTML 和 JavaScript 中都進行修改,不建議使用 HTML 事件處理程序,而建議使用 JavaScript 指定事件處理程序的主要原因。
也就是在 js 中,當真正需要使用 onclick 時,使用如下方式:
let btn = document.getElementById("myBtn");
btn.onclick = function(e) {
console.log(e.type); // "click"
};