曾經寫過一篇隨筆,attachEvent和addEventListener,跟本文內容有不少類似之處javascript
本文連接:javascript之事件綁定html
一、原始寫法前端
<div onclick="alert('you clicked me just now);'">click me</div>
在剛開始學習前端的時候,咱們難免這麼將事件綁定寫在html中,後來咱們想將html和js腳本進行分離便這麼寫java
<div id="test">click me</div> <script type="text/javascript"> test.onclick=function(){ alert("you click me just now"); }; </script>
這麼寫其實和上一種寫法在執行上是沒有任何分別的,只是他略微顯得「高大上」一點。在此,須要感謝八樓@con的提醒,其實他們仍是有一點點的區別,由於它們的執行環境是稍微有點不一樣的,其函數做用域中包含的對象是不同的。詳情可看本文的留言。jquery
當咱們寫更復雜一些的腳本的時候,發現這種「對象.事件=事件處理函數」方式並很差,由於後面的事件明顯得會覆蓋前一個事件處理函數,屢次事件綁定的結果每每是僅執行最後一個事件處理函數,能夠看attachEvent和addEventListener文中示例。程序員
後來,咱們必須在實際開發中放棄這種「非主流」的寫法了。api
2.attachEvent和addEventListener瀏覽器
首先得說明,attachEvent是僅在萬惡的IE是下可行的,addEventListener是在其它遵循W3C標準的瀏覽器中可行(經常使用的瀏覽器能夠放心使用addEventListener),而且在IE9和以後的版本中也可使用addEventListener。看來在大勢所趨下,MS不得不妥協了。ide
在此得說明,上述的不管是「對象.事件=事件處理函數」的方式,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
全文中若是有不正確的地方,請斧正。