1. 程式人生 > >DOM的預設事件、事件模型、事件委託、阻止預設事件、冒泡事件的方式等。

DOM的預設事件、事件模型、事件委託、阻止預設事件、冒泡事件的方式等。

DOM0,DOM2,DOM3事件,事件基礎知識入門

  事件是javascript和HTML互動基礎, 任何文件或者瀏覽器視窗發生的互動, 都要通過繫結事件進行互動;
  事件有DOM0, DOM2和DOM3的區分(別問我怎麼少了一個DOM1, 也沒找到DOM1的資訊啊,);

  DOM0就是直接通過 onclick寫在html裡面的事件, 比如:

<input onclick="alert(1)" />

  DOM2是通過addEventListener繫結的事件, 還有IE下的DOM2事件通過attachEvent繫結;
  DOM3是一些新的事件, 區別DOM3和DOM2的方法我感覺是DOM3事件有分大小寫的,DOM2沒有;

  事件流
  事件流
  
  IE的事件流是冒泡, 從裡面往上面冒, netscape是從外部元素往內部元素捕獲;
  而DOM2級的事件規定了事件流包含三個階段包括: 1:事件捕獲, 2:處於目標階段, 3:事件冒泡階段(IE8以及更早版本不支援DOM事件流);

DOM0事件

事件模型在不斷髮展,早期的事件模型稱為DOM0級別。

DOM0事件模型,所有的瀏覽器都支援。直接在dom物件上註冊事件名稱,就是DOM0寫法.

  document.getElementById("test").onclick = function(e){}; 

意思就是註冊一個onclick事件。當然,它和這種寫法是一個意思:

document.getElementById("test")["onmousemove"] = function(e){};

  
基於DOM0的事件,對於同一個dom節點而言,只能註冊一個,後邊註冊的同種事件會覆蓋之前註冊的。接下來再說說this。事件觸發時,this就是指該事件在哪個dom物件上觸發。想解除事件就相當簡單了,只需要再註冊一次事件,把值設成null

var btn = document.getElementById("test");
        btn.onclick = function(e){
          alert("ok");
        };

原理就是最後註冊的事件要覆蓋之前的,最後一次註冊事件設定成null,也就解除了事件繫結。

  DOM0的事件具有極好的跨瀏覽器優勢, 會以最快的速度繫結, 如果你通過DOM2繫結要等到JS執行, DOM0不用, 因為DOM0是寫在元素上面的;

DOM2事件

DOM2支援同一dom元素註冊多個同種事件。
DOM2新增了捕獲和冒泡的概念。
DOM2事件通過addEventListener和removeEventListener管理,當然,這是標準。

註冊事件時,通常使用捕獲或冒泡的。事件只會因為捕獲或者冒泡觸發一次.
addEventListener當然就是註冊事件,她有三個引數,分別為:”事件名稱”, “事件回撥”, “捕獲/冒泡”

    obj.addEventListener("click", func, true); // 捕獲方式
    obj.addEventListener("click", func, false); // 冒泡方式

DOM2事件的冒泡和捕獲
事件名稱就不用多說了,相比DOM0,去掉了前邊的on而已。
事件回撥也很好理解,事件觸發了總得通知你吧!回撥時和DOM0一樣,也會預設傳入一個event引數,同時this是指觸發該事件的dom節點。
最後一個引數是布林型,true代表捕獲事件,false代表冒泡事件。

   <html xmlns="http://www.w3.org/1999/xhtml">
        <head>
            <title></title>
            <meta charset="utf-8" />
            <style type="text/css">
                #p { width: 300px; height: 300px; padding: 10px;  border: 1px solid black; }
                #c { width: 100px; height: 100px; border: 1px solid red; }
            </style>
        </head>
        <body>
        <div id="p">
            parent
            <div id="c">
                child
            </div>
        </div>
        <script type="text/javascript">
            var p = document.getElementById('p'),
                    c = document.getElementById('c');
            c.addEventListener('click', function () {
                alert('子節點捕獲')
            }, true);

            c.addEventListener('click', function (e) {
                alert('子節點冒泡')
            }, false);

            p.addEventListener('click', function () {
                alert('父節點捕獲')
            }, true);

            p.addEventListener('click', function () {
                alert('父節點冒泡')
            }, false);
        </script>
        </body>
        </html>

  這個依次會打出父節點捕獲,子節點捕獲,子節點冒泡和父節點冒泡,(注意:如果你在目標元素上改變繫結事件的順序, 這些事件可能就不按照捕獲和冒泡的順序來了,而是根據捕獲和冒泡的順序進行觸發 , 這個有解決方法,參考:) ==>>(葉小釵的東東)http://www.cnblogs.com/yexiaochai/p/3567597.html );

  捕獲的事件是按照順序執行的, 而冒泡的事件在有的瀏覽器中的按照順序執行有的按照相反的順序執行;
  說實話啊,事件捕獲沒啥用處;

  還有一點要注意的是:元素點選裡面的this有問題哦, 在IE8和和IE8以前, 通過attachEvent繫結的事件裡面的this是window;

        <html xmlns="http://www.w3.org/1999/xhtml">
        <head>
            <title></title>
            <meta charset="utf-8" />
            <style type="text/css">
                #p { width: 300px; height: 300px; padding: 10px;  border: 1px solid black; }
            </style>
        </head>
        <body>
        <div id="p">
            p
        </div>
        <script type="text/javascript">
            var p = document.getElementById('p');
            p.attachEvent("onclick",function(){
                alert(this);
            })
            //在IE5678這個彈出的是window哦,這個要主要, 要讓this 指向這個元素通過apply或者call改變上下文
        </script>
        </body>
        </html>

  chrome下有個getEventListeners可以獲取元素繫結事件, 從小釵哪裡抄的,程式碼如下:

<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title></title>
</head>
<body>
<div id="d">ddssdsd</div>
  <script type="text/javascript">
    var node = document.getElementsByTagName('*');
    var d = document.getElementById('d');
    d.addEventListener('click', function () {
      alert();
    }, false);
    d.addEventListener('click', function () {
      alert('我是第二次');
    }, false);
    d.onclick = function () {
      alert('不規範的繫結');
    }
    d.addEventListener('click', function () {
      alert();
    }, true);

    d.addEventListener('mousedown', function () {
      console.log('mousedown');
    }, true);
    var evets = typeof getEventListeners == 'function' && getEventListeners(d);
    getEventListeners(d)
  </script>
</body>
</html>

——-這裡我試了下報錯:index_3.html:87 Uncaught ReferenceError: getEventListeners is not defined———chrome下——–直接除錯中可用:
Chrome DevTools->console->getEventListeners(d);

  這個相容問題常見的解決方法,一般來說夠用了:

    <script>
     var eventUtil = {
        add : function(el, type, handler) {
            if(el.addEventListener) {
                el.addEventListener(type, handler, false);
            }else if( el.attachEvent ) {
                el.attachEvent("on"+type, handler);
            }else{
                el["on"+type] = handler;
            }
        },
        off : function(el, type, handler) {
            if( el.removeEventListener ) {
                el.removeEventListener(type, handler, false)
            }else if( el.detachEvent ) {
                el.detachEvent(type, handler);
            }else{
                el["on"+type] = null;
            }
        }
     };
    </script>

其實,事件觸發時,會預設傳入一個event物件,前邊提過了,這個event物件上有一個方法:stopPropagation,通過此方法,可以阻止冒泡,這樣外層div就接收不到事件了。程式碼如下:

var btn = document.getElementById("test");
var btnInner = document.getElementById("testInner");

btn.addEventListener("click", function(e){
  alert("ok1");
}, false);

btnInner.addEventListener("click", function(e){
  //阻止冒泡
e.stopPropagation();
  alert("ok");
}, false);

終於要說說怎麼解除事件了。解除事件語法:btn.removeEventListener(“事件名稱”, “事件回撥”, “捕獲/冒泡”);
這和繫結事件的引數一樣,詳細說明下:

      ·  事件名稱,就是說解除哪個事件唄。
      ·  事件回撥,是一個函式,這個函式必須和註冊事件的函式是同一個。
      ·  事件型別,布林值,這個必須和註冊事件時的型別一致。

也就是說,名稱、回撥、型別,三者共同決定解除哪個事件,缺一不可。

事件物件

  無論在DOM0還是DOM2還是DOM3中都會在事件函式中傳入事件物件;

    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title></title>
        <meta charset="utf-8" />
        <style type="text/css">
            #p { width: 300px; height: 300px; padding: 10px;  border: 1px solid black; }
        </style>
    </head>
    <body>
    <div id="p">
        p
    </div>
    <script type="text/javascript">
        var p = document.getElementById('p');
        p.addEventListener("click",function(){
            console.log(arguments[0]);
        })
    </script>
    </body>
    </html>

  事件物件event下的屬性和方法:

因為各個瀏覽器的事件物件不一樣, 把主要的時間物件的屬性和方法列出來;
bubble :    表明事件是否冒泡
cancelable :  表明是否可以取消冒泡
currentTarget : 當前時間程式正在處理的元素, 和this一樣的;
defaultPrevented: false ,如果呼叫了preventDefualt這個就為真了;
detail: 與事件有關的資訊(滾動事件等等)
eventPhase: 如果值為1表示處於捕獲階段, 值為2表示處於目標階段,值為三表示在冒泡階段
target || srcElement: 事件的目標
trusted: 為ture是瀏覽器生成的,為false是開發人員建立的(DOM3)
type : 事件的型別
view : 與元素關聯的window, 我們可能跨iframe;
preventDefault()    取消預設事件;
stopPropagation() 取消冒泡或者捕獲;
stopImmediatePropagation() (DOM3)阻止任何事件的執行;
//stopImmediatePropagation阻止 繫結在事件觸發元素的 其他同類事件的callback的執行

IE下的事件物件是在window下的,而標準應該作為一個引數, 傳為函式第一個引數;
IE的事件物件定義的屬性跟標準的不同,如:
cancelBubble 預設為false, 如果為true就是取消事件冒泡;
returnValue 預設是true,如果為false就取消預設事件;
srcElement, 這個指的是target, Firefox下的也是srcElement;

兩個誤區:

  1. 在同一個物件上註冊事件,並不一定按照註冊順序執行

這一點,從上面的例子可以看出,你隨便打亂四個事件繫結的順序,結果一般不變!出現這樣結果的原因是存在捕獲模式和冒泡模式。之所以如此是因為事件目的地節點既綁定了冒泡事件也綁定了捕獲事件,此時的執行順序按照繫結的先後順序執行(情況比較少見)。

  1. event.stopPropagation();就是阻止事件的冒泡

這個表述不能說他錯誤,但是是不完整的,他除了阻止事件的冒泡,還阻止事件的繼續捕獲,簡而言之就是阻止事件的進一步傳播。

事件委託:

1,什麼是事件委託:通俗的講,事件就是onclick,onmouseover,onmouseout,等就是事件,委託呢,就是讓別人來做,這個事件本來是加在某些元素上的,然而你卻加到別人身上來做,完成這個事件。

也就是:利用冒泡的原理,把事件加到父級上,觸發執行效果。

好處呢:1,提高效能。

我們可以看一個例子:需要觸發每個li來改變他們的背景顏色。

<ul id="ul">
  <li>aaaaaaaa</li>
  <li>bbbbbbbb</li>
  <li>cccccccc</li>
</ul>

window.onload = function(){
  var oUl = document.getElementById("ul");
  var aLi = oUl.getElementsByTagName("li");

  for(var i=0; i<aLi.length; i++){
    aLi[i].onmouseover = function(){
      this.style.background = "red";
    }
    aLi[i].onmouseout = function(){
      this.style.background = "";
    }
  }
}

這樣我們就可以做到li上面新增滑鼠事件。

但是如果說我們可能有很多個li用for迴圈的話就比較影響效能。

下面我們可以用事件委託的方式來實現這樣的效果。html不變

window.onload = function(){
  var oUl = document.getElementById("ul");
  var aLi = oUl.getElementsByTagName("li");

/*
這裡要用到事件源:event 物件,事件源,不管在哪個事件中,只要你操作的那個元素就是事件源。
ie:window.event.srcElement
標準下:event.target
nodeName:找到元素的標籤名
*/
  oUl.onmouseover = function(ev){
    var ev = ev || window.event;
    var target = ev.target || ev.srcElement;
    //alert(target.innerHTML);
    if(target.nodeName.toLowerCase() == "li"){
    target.style.background = "red";
    }
  }
  oUl.onmouseout = function(ev){
    var ev = ev || window.event;
    var target = ev.target || ev.srcElement;
    //alert(target.innerHTML);
    if(target.nodeName.toLowerCase() == "li"){
    target.style.background = "";
    }
  }
}

好處2,新新增的元素還會有之前的事件。

我們還拿這個例子看,但是我們要做動態的新增li。點選button動態新增li

如:

<input type="button" id="btn" />
<ul id="ul">
  <li>aaaaaaaa</li>
  <li>bbbbbbbb</li>
  <li>cccccccc</li>
</ul>

不用事件委託我們會這樣做:

window.onload = function(){
  var oUl = document.getElementById("ul");
  var aLi = oUl.getElementsByTagName("li");
  var oBtn = document.getElementById("btn");
  var iNow = 4;
  for(var i=0; i<aLi.length; i++){
    aLi[i].onmouseover = function(){
      this.style.background = "red";
    }
    aLi[i].onmouseout = function(){
      this.style.background = "";
    }
  }

  oBtn.onclick = function(){
    iNow ++;
    var oLi = document.createElement("li");
    oLi.innerHTML = 1111 *iNow;
    oUl.appendChild(oLi);
  }


}

這樣做我們可以看到點選按鈕新加的li上面沒有滑鼠移入事件來改變他們的背景顏色。

因為點選新增的時候for迴圈已經執行完畢。

那麼我們用事件委託的方式來做。就是html不變

window.onload = function(){
  var oUl = document.getElementById("ul");
  var aLi = oUl.getElementsByTagName("li");
  var oBtn = document.getElementById("btn");
  var iNow = 4;

  oUl.onmouseover = function(ev){
    var ev = ev || window.event;
    var target = ev.target || ev.srcElement;
    //alert(target.innerHTML);
    if(target.nodeName.toLowerCase() == "li"){
    target.style.background = "red";
    }
  }
  oUl.onmouseout = function(ev){
    var ev = ev || window.event;
    var target = ev.target || ev.srcElement;
    //alert(target.innerHTML);
    if(target.nodeName.toLowerCase() == "li"){
    target.style.background = "";
    }
  }
  oBtn.onclick = function(){
    iNow ++;
    var oLi = document.createElement("li");
    oLi.innerHTML = 1111 *iNow;
    oUl.appendChild(oLi);
  }
}

阻止預設事件

http://www.2cto.com/kf/201412/359961.html
  
有一些html元素預設的行為,比如說a標籤,點選後有跳轉動作;form表單中的submit型別的input有一個預設提交跳轉事件;reset型別的input有重置表單行為。

如果你想阻止這些瀏覽器預設行為,JavaScript為你提供了方法。

先上程式碼

 var $a = document.getElementsByTagName("a")[0];
 $a.onclick = function(e){
     alert("跳轉動作被我阻止了")
     e.preventDefault();
     //return false;//也可以
 }

預設事件沒有了。

既然return false 和 e.preventDefault()都是一樣的效果,那它們有區別嗎?當然有。

僅僅是在HTML事件屬性 和 DOM0級事件處理方法中 才能通過返回 return false 的形式組織事件宿主的預設行為。

阻止冒泡事件:

function stopBubble(e){
  if(e&&e.stopPropagation){//非IE
   e.stopPropagation();
  }
  else{//IE
   window.event.cancelBubble=true;
  }
 }