昨天回答了一個關於vue的問題 vue 除了input 其他可以用keyup事件嘛? 在vue中沒有提供除表單之外其它的keyup綁定方法,可以使用原生的監控鍵盤的事件,於是給出了代碼:
mounted() { document.onkeydown=function(e) { if(e && e.keyCode==83 && e.altKey ){ //同時按下Alt+s //要做的事情 } } }
這段代碼本身沒有什么錯誤,但是一位知友評論為什么不用addEventListener呢?addEventListener也確實可以,那么addEventListener和on之間有什么區別呢?
查閱addEventListener文檔發現他們還確實有點區別
為什么要使用 addEventListener
?
addEventListener
是 W3C DOM 規范中提供的注冊事件監聽器的方法。它的優點包括:
- 它允許給一個事件注冊多個
listener
。當存在其他的庫時,使用 DHTML 庫或者 Mozilla extensions 不會出現問題。 - 它提供了一種更精細的手段控制
listener
的觸發階段。(即可以選擇捕獲或者冒泡)。 - 它對任何 DOM 元素都是有效的,而不僅僅只對 HTML 元素有效。
在事件分派時添加事件處理器
當一個 EventListener
在 EventTarget
正在處理事件的時候被注冊到 EventTarget
上,它不會被立即觸發,但可能在事件流后面的事件觸發階段被觸發,例如可能在捕獲階段添加,然后在冒泡階段被觸發。
多個相同的事件處理器
同一個 EventTarget 注冊了多個相同的 EventListener
,那么重復的實例會被拋棄。所以這么做不會使得 EventListener
被調用兩次,也不需要用 removeEventListener 手動清除多余的EventListener
,因為重復的都被自動拋棄了。
處理過程中 this
的值的問題
通常來說this的值是觸發事件的元素的引用,這種特性在多個相似的元素使用同一個通用事件監聽器時非常讓人滿意。
當使用 addEventListener()
為一個元素注冊事件的時候,句柄里的 this 值是該元素的引用。其與傳遞給句柄的 event 參數的 currentTarget 屬性的值一樣。
比如下面的例子:
<table id="t" onclick="modifyText();">
...
這時modifyText()
中的this
的值會變成全局 (window) 對象的引用(在嚴格模式中為 undefined);
注意: JavaScript 1.8.5 引入了Function.prototype.bind() 方法,允許制定函數調用時的 this 的值。這使得想要繞開由於調用情況不同,this 取值不同的問題變得十分容易 。然而請注意,你應該保留一個 listener 的引用,以便在未來需要的時候能夠比較好地移除。
下面是 bind
相關的例子:
var Something = function(element) { // |this| is a newly created object this.name = 'Something Good'; this.onclick1 = function(event) { console.log(this.name); // undefined, as |this| is the element }; this.onclick2 = function(event) { console.log(this.name); // 'Something Good', as |this| is bound to newly created object }; element.addEventListener('click', this.onclick1, false); element.addEventListener('click', this.onclick2.bind(this), false); // Trick } var s = new Something(document.body);
上面這個例子的一個問題是不可能移除使用了 bind
的 listener。一種解決辦法是使用定制的函數去捕獲任意類型:
var Something = function(element) { // |this| is a newly created object this.name = 'Something Good'; this.handleEvent = function(event) { console.log(this.name); // 'Something Good', as this is bound to newly created object switch(event.type) { case 'click': // some code here... break; case 'dblclick': // some code here... break; } }; // Note that the listeners in this case are |this|, not this.handleEvent element.addEventListener('click', this, false); element.addEventListener('dblclick', this, false); // You can properly remove the listeners element.removeEventListener('click', this, false); element.removeEventListener('dblclick', this, false); } var s = new Something(document.body);
傳統的 Internet Explorer 及其 attachEvent 方法
對於 Internet Explorer 來說,在IE 9之前,你必須使用 attachEvent
而不是使用標准方法 addEventListener
。為了支持IE,前面的例子需要改成這樣:
if (el.addEventListener) { el.addEventListener('click', modifyText, false); } else if (el.attachEvent) { el.attachEvent('onclick', modifyText); }
使用 attachEvent
方法有個缺點,this
的值會變成 window
對象的引用而不是觸發事件的元素。
兼容性
Note: IE8 不具有任何替代 useCapture 的方法,useCapture 是 IE8 不支持的。 請注意下面的代碼只能添加 IE8。另外請注意,下面這個 IE8 polyfill 只適用於標准模式:需要 DOCTYPE 聲明。
(function() { if (!Event.prototype.preventDefault) { Event.prototype.preventDefault=function() { this.returnValue=false; }; } if (!Event.prototype.stopPropagation) { Event.prototype.stopPropagation=function() { this.cancelBubble=true; }; } if (!Element.prototype.addEventListener) { var eventListeners=[]; var addEventListener=function(type,listener /*, useCapture (will be ignored) */) { var self=this; var wrapper=function(e) { e.target=e.srcElement; e.currentTarget=self; if (typeof listener.handleEvent != 'undefined') { listener.handleEvent(e); } else { listener.call(self,e); } }; if (type=="DOMContentLoaded") { var wrapper2=function(e) { if (document.readyState=="complete") { wrapper(e); } }; document.attachEvent("onreadystatechange",wrapper2); eventListeners.push({object:this,type:type,listener:listener,wrapper:wrapper2}); if (document.readyState=="complete") { var e=new Event(); e.srcElement=window; wrapper2(e); } } else { this.attachEvent("on"+type,wrapper); eventListeners.push({object:this,type:type,listener:listener,wrapper:wrapper}); } }; var removeEventListener=function(type,listener /*, useCapture (will be ignored) */) { var counter=0; while (counter<eventListeners.length) { var eventListener=eventListeners[counter]; if (eventListener.object==this && eventListener.type==type && eventListener.listener==listener) { if (type=="DOMContentLoaded") { this.detachEvent("onreadystatechange",eventListener.wrapper); } else { this.detachEvent("on"+type,eventListener.wrapper); } eventListeners.splice(counter, 1); break; } ++counter; } }; Element.prototype.addEventListener=addEventListener; Element.prototype.removeEventListener=removeEventListener; if (HTMLDocument) { HTMLDocument.prototype.addEventListener=addEventListener; HTMLDocument.prototype.removeEventListener=removeEventListener; } if (Window) { Window.prototype.addEventListener=addEventListener; Window.prototype.removeEventListener=removeEventListener; } } })();
注冊 listener
的舊方法
addEventListener()
在DOM 2 Events 規范中引入。在這之前,事件監聽器應該用以下的方法注冊:
// Pass a function reference — do not add '()' after it, which would call the function! el.onclick = modifyText; // Using a function expression element.onclick = function() { // ... function logic ... };
這個方法會替換這個元素上所有已存在的 onclick
事件。對於其他事件是類似的,比如 blur
(onblur
)、 keypress
(onkeypress
)等等。
由於這是 DOM 0 規范的基本內容,幾乎所有瀏覽器都支持這個,而且不需要特殊的跨瀏覽器兼容代碼。因此通常這個方法被用於動態地注冊時間處理器,除非必須使用 addEventListener()
才能提供的特殊特性。
內存問題
var i; var els = document.getElementsByTagName('*'); // Case 1 for(i=0 ; i<els.length ; i++){ els[i].addEventListener("click", function(e){/*do something*/}, false}); } // Case 2 function processEvent(e){ /*do something*/ } for(i=0 ; i<els.length ; i++){ els[i].addEventListener("click", processEvent, false}); }
在第一種情況下,每個循環中都會創建一個新的(匿名)函數。在第二種情況下,會使用先前聲明的相同的函數作為事件處理器。這樣的結果是占用的存儲空間更小。而且,在第一種情況中,由於沒有保持到匿名函數的引用,它不可能被調用 element.removeEventListener
,這是因為我們沒有一個可參考的處理器,而在第二種情況,它可以被 myElement.removeEventListener("click", processEvent, false)
。
使用 passive 改善的滾屏性能
var elem = document.getElementById('elem'); elem.addEventListener('touchmove', function listener() { /* do something */ }, { passive: true });
example:
<div class="box">ooxx</div>
window.onload = function(){ var box = document.getElementById("box"); box.onclick = function(){ console.log("我是box1"); } box.onclick = function(){ box.style.fontSize = "18px"; console.log("我是box2"); } }
運行結果:“我是box2”
第二個onclick把第一個onclick給覆蓋了,雖然大部分情況我們用on就可以完成我們想要的結果,但是有時我們又需要執行多個相同的事件,很明顯如果用on完成不了我們想要的,那不用猜,你們肯定知道了,對!addEventListener可以多次綁定同一個事件並且不會覆蓋上一個事件。
用addEventListener的代碼:
window.onload = function(){ var box = document.getElementById("box"); box.addEventListener("click",function(){ console.log("我是box1"); }) box.addEventListener("click",function(){ console.log("我是box2"); }) } 運行結果:我是box1 我是box2
addEventListenert方法第一個參數填寫事件名,注意不需要寫on,第二個參數可以是一個函數,第三個參數是指在冒泡階段還是捕獲階段處理事件處理程序,如果為true代表捕獲階段處理,如果是false代表冒泡階段處理,第三個參數可以省略,大多數情況也不需要用到第三個參數,不寫第三個參數默認false
第三個參數的使用:
有時候的情況是這樣的
<body>
<div id="box">
<div id="child"></div>
</div>
</body>
如果我給box加click事件,如果我直接單擊box沒有什么問題,但是如果我單擊的是child元素,那么它是怎么樣執行的?(執行順序)
box.addEventListener("click",function(){ console.log("box"); }) child.addEventListener("click",function(){ console.log("child"); }) 執行的結果: child box
也就是說,默認情況事件是按照事件冒泡的執行順序進行的。
如果第三個參數寫的是true,則按照事件捕獲的執行順序進行的。
box.addEventListener("click",function(){ console.log("box"); },true) child.addEventListener("click",function(){ console.log("child"); }) 執行的結果: box child
事件冒泡執行過程:
從最具體的的元素(你單擊的那個元素)開始向上開始冒泡,拿我們上面的案例講它的順序是:child->box
事件捕獲執行過程:
從最不具體的元素(最外面的那個盒子)開始向里面冒泡,拿我們上面的案例講它的順序是:box->child