曾經寫過一篇隨筆,attachEvent和addEventListener,跟本文內容有很多相似之處
本文鏈接:javascript之事件綁定
1、原始寫法
<div onclick="alert('you clicked me just now);'">click me</div>
在剛開始學習前端的時候,我們不免這么將事件綁定寫在html中,后來我們想將html和js腳本進行分離便這么寫
<div id="test">click me</div> <script type="text/javascript"> test.onclick=function(){ alert("you click me just now"); }; </script>
這么寫其實和上一種寫法在執行上是沒有任何分別的,只是他略微顯得“高大上”一點。在此,需要感謝八樓@con的提醒,其實他們還是有一點點的區別,因為它們的執行環境是稍微有點不同的,其函數作用域中包含的對象是不一樣的。詳情可看本文的留言。
當我們寫更復雜一些的腳本的時候,發現這種“對象.事件=事件處理函數”方式並不好,因為后面的事件明顯得會覆蓋前一個事件處理函數,多次事件綁定的結果往往是僅執行最后一個事件處理函數,可以看attachEvent和addEventListener文中示例。
后來,我們必須在實際開發中放棄這種“非主流”的寫法了。
2.attachEvent和addEventListener
首先得說明,attachEvent是僅在萬惡的IE是下可行的,addEventListener是在其它遵循W3C標准的瀏覽器中可行(常用的瀏覽器可以放心使用addEventListener),並且在IE9和之后的版本中也可以使用addEventListener。看來在大勢所趨下,MS不得不妥協了。
在此得說明,上述的無論是“對象.事件=事件處理函數”的方式,attachEvent事件綁定的方式還是addEventListener不帶第三個參數的情形下,都是冒泡型事件處理方式。至於什么是冒泡事件什么是捕獲事件,這個涉及到DOM文檔對象模型和事件流。簡單來說就是,冒泡就是事件的傳播方式是從事件目標節點到DOM文檔結構的根節點方向傳播,而捕獲型事件就是從DOM文檔結構的根節點到事件目標節點。
obj = document.getElementById("testdiv"); obj.attachEvent('onclick',function(){{alert('1');}); obj.attachEvent('onclick',function(){{alert('2');}); obj.attachEvent('onclick',function(){{alert('3');}); //執行順序是alert(3),alert(2),alert(1);
obj = document.getElementById("testdiv"); obj.addEventListener('click',function(){{alert('1');},false); obj.addEventListener('click',function(){{alert('2');},false); obj.addEventListener('click',function(){{alert('3');},false); //點擊obj對象時,執行順序為alert('1'),alert('2'),alert('3');
從這段例子可以看出:
對於同一個DOM對象綁定多個事件處理函數時,attachEvent是先綁定后執行,而addEventListener是先綁定先執行,這么看來attachEvent綁定的事件不符合程序員開發的思路啊,我后綁定的事件處理函數卻要先執行,完全不遵循“先來后到”的規矩,搞得莫名其妙,看來這個attachEvent在不遠的將來應該會被淘汰。
attachEvent在事件綁定的事件還必須加上這個“on”,如果不注意的話很容易忘記添加,這個“on”關鍵字可能是從那種原始寫法中沒有進化完全吧。
當了解完了這兩個函數的區別后,我們可以寫一些共同方法用來兼容IE和其它的瀏覽器
function addEvent(elm, evType, fn, useCapture) { if (elm.addEventListener) { elm.addEventListener(evType, fn, useCapture); // W3C標准,根據useCapture來判斷是冒泡事件還是捕獲事件 return true; } else if (elm.attachEvent) { var r = elm.attachEvent(‘on‘ + evType, fn);//IE5+,僅支持冒泡事件 return r; } else { elm['on' + evType] = fn;//DOM事件 } }
當然這個方法也有弊端,在IE8跟之前版本下,還是事件的后綁定先執行且一直都是冒泡事件。或者使用如下的方法:

var addEvent = (function () { if (document.addEventListener) { return function (el, type, fn) { if (el.length) { for (var i = 0; i && el.length; i++) { addEvent(el[i], type, fn); } } else { el.addEventListener(type, fn, false); } }; } else { return function (el, type, fn) { if (el.length) { for (var i = 0; i && el.length; i++) { addEvent(el[i], type, fn); } } else { el.attachEvent('on' + type, function () { return fn.call(el, window.event); }); } }; } })();
這些都是原生的腳本事件綁定處理方式,顯得低調有內涵。
繼續
<div id="a1" style="float: left; width: 200px; height: 200px; background-color: red;"> a1 <div id="a2" style="float: left; width: 100px; height: 100px; background-color: blue;">a2</div> </div> <script type="text/script"> a1.addEventListener('click', function (e) { console.log('a1'); }); a2.addEventListener('click', function (e) { console.log('a2'); e.stopPropagation(); }); </script>
在a2的事件處理函數中添加了e.stopPropagation(),這句代碼可以阻止事件的繼續傳播(無論是繼續捕獲階段還是繼續冒泡階段),在實際開發中應該會經常性的用到,IE還是不支持。Event.preventDefault()可以阻止事件目標的默認動作,IE也是不支持的。
3.jQ的bind,delegate,on和live
有了jQ之后,所有的事件綁定都變得輕而易舉了。bind,delegate,on和live他們的使用在此就不做贅述,jQ api上都有詳細講解。
首先說bind,bind已經很好的解決了IE的attachEvent先綁定后執行的問題了,請看
<div id="a1" style="float: left; width: 200px; height: 200px; background-color: red;"> a1 </div> <script src="jquery-1.10.2.js" type="text/javascript"></script> <script type="text/javascript"> $('#a1').bind('click', function () { console.log('1'); }).bind('click', function () { console.log('2'); }); </script>
點擊a1后,打出的log是先1后2,跟事件的綁定順序一致。
在jQ1.7之前的版本中,bind方法會直接將事件處理函數附加到元素上,而事件處理函數被添加到當前元素的jQuery對象上,當有很多元素綁定時間處理函數時,這時便需要大量的存儲空間來存儲這個事件處理函數了。那個時候,推薦使用的方法是live,因為live是將事件處理函數添加document對象上,因此可以省去為每個元素都附加存儲事件處理函數的空間。
不過,jQ1.7之后的版本中,有了on方法,live方法也就被取消了,on方法將事件處理函數添加到了當前選定的jQuery對象上。其實它等價於delegate方法。
<ul id="ul"> <li></li> <li></li> <li></li> <li></li> <li></li> <li></li> <li></li> <ul> <script type='text/javascript'> $('#ul').on('mouseover','li',function(){alert('1');}); </script>
上例中,將事件處理函數function(){alert('1');}附加到的對象就是ul。
delegate是事件委托,因為是委托,事件綁定在ul上,動態在ul里添加的元素也能激發上述綁定的事件處理函數
<script type='text/javascript'> $('#ul').delegate('li','mouseover',function(){alert('1');}); </script>
看一下jQ源碼就知道,delegate等價於on方法。
delegate: function( selector, types, data, fn ) { return this.on( types, selector, data, fn ); },
綜上所述,在jQ中推薦的事件綁定方式是delegate、on,它們是等價的,在1.7之后的版本中live方法不可以使用,bind方法也可以綁定事件,但是最好是簡單元素結構下使用bind。
4.關於事件處理函數的移除
<div id="test">aaa</div> <script type="text/javascript"> test.onclick = function(){alert('1')}; test.onclick = null; </script>
這種方式其實就是覆蓋。
針對於attachEvent和addEventListener兩個函數,他們的解除方法分別是detachEvent和removeEventListener
obj = document.getElementById("testdiv"); obj.detachEvent('onclick',function(){{alert('1');}); obj.detachEvent('onclick',function(){{alert('2');}); obj.detachEvent('onclick',function(){{alert('3');});
obj = document.getElementById("testdiv"); obj.removeEventListener('click',function(){{alert('1');},false); obj.removeEventListener('click',function(){{alert('2');},false); obj.removeEventListener('click',function(){{alert('3');},false);
對於jQ的事件解除綁定分別是
bind---->unbind
on---->off
live---->die
delegate---->undelegate
等等
為了防止jQ事件解除的時候將所有的方法都給解除了,可以給事件綁定時添加命名空間來區分
$element.delegate('.boot','click.dismiss.modal',fn1) .delegate('.boot','dblclick.dismiss',fn2)
.delegate('.boot','mouseover',fn3); //do sth $element.undelegate('.modal');//僅將fn1解除綁定 //do sth $element.undelegate('.dismiss');//能夠將fn1和fn2都解除綁定,但fn1已經解除 ,此處實現的效果是解除fn2
//do sth
$element.undelegate(); //解除所有事件,此處僅解除fn3
另外,如果我們想方法綁定后僅執行一次的話,可以使用jQ的one方法,省去我們解除事件綁定的過程。
$element.one('dblclcik',function(){ // do sth }); // 可以省去解綁這個過程,僅能執行一次
事件綁定的方式很多,需要看時機過程中的需求,需要考慮的因素是簡便,簡介,性能好,同時需要兼容各種瀏覽器,做到這些就可以以不變應萬變了。
本文參考:attachEvent和addEventListener
全文中如果有不正確的地方,請斧正。