文章出處

DOM事件模型

在0級DOM事件模型中,它只是簡單的執行你為它綁定的事件,比如你為某個元素添加了一個onclick事件,當事件觸發時,它只是去調用我們綁定的那個方法,不再做其他的操作。

在2級DOM事件模型中,就比較復雜一些,它將不再是單純的調用一下自身綁定的事件就完事了,它還擁有機會去處理它的祖先節點,在DOM2級事件模型中,它有一個事件傳播過程,分為3個階段,從“事件捕獲”Document開始來到“目標節點”再從“目標節點”冒泡回Document對象,舉段代碼

  <div id="div">
    <a href="javascript:;">DOM事件模型</a>
  </div>
  <script>
    var div = document.getElementById("div");
    var a = div.children[0];
    document.onclick = function(){
      console.log("document");
    };
    a.onclick = function(){
      console.log("a");
    };
    div.onclick = function(){
      console.log("div");
    };
  </script>

可以看到我只是點擊了a元素,但是div和document綁定的事件也被觸發了,這就是DOM2級和1級的區別,同時你也看到,它是先輸出的a,而不是div和document,雖然說它有3個階段,但瀏覽器默認是在冒泡階段才執行的,如果不這樣的話,我們點擊a元素就會執行多次啦。

如果你想讓瀏覽器在捕獲階段執行,那么就不能直接使用onclick添加事件了,而是要使用addEventListener添加事件,它的第三個參數就是用來設置在哪個階段執行,具體可以看 http://www.runoob.com/jsref/met-element-addeventlistener.html

currentTarget

event.currentTarget獲取到的是當前綁定事件的那個對象,也因為此原因,他獲取到的常常和this一樣,下面是一個示例:

  <a href="javascript:;" id="a">evnet.currentTarget</a>
  <script>
    var a = document.getElementById("a");
    a.onclick = function(event){
      console.log("this:",this);
      console.log("currentTarget:",event.currentTarget);
    };
  </script>

當我點擊a標簽時,this和currentTarget打印出來的都是a標簽本身,如下圖

既然如此這個currentTarget有啥用呢,這是一開始的想法,但隨后發現不對,想到這個currentTarget始終獲取到的是那個綁定事件的對象,但this卻有很大的不同,因為this并不關心是誰綁定的它,它只關心是誰執行的它,因此如果再將上面那段代碼改造改造,我們就會發現,它們真的是不一樣的,代碼如下:

var a = document.getElementById("a");
    a.onclick = function(event){
      (function(){
        console.log("this:",this);
        console.log("currentTarget:",event.currentTarget);
      }());
    };

效果如圖

這也就是說,某些時候如果不能通過this來獲取綁定事件的對象時,就可以使用event.currentTarget。

有些人認為event.currentTarget就是this,其實不然,容易把event.currentTarget當成this,主要原因就是,在事件處理器中,我們常常使用的是this,而不是event.currentTarget,至于為什么,反正我是因為從接觸js開始,所看過的教程上都是那么用的,時間長了,竟然忘了一件事,event.currentTarget才是真的屬于事件處理器的,而this不過是個冒牌貨。

target

event.target獲取到的是觸發事件的那個元素。

網上常說的事件委派,就是通過event.target來實現的,所謂的事件委派,就是我并不給某個具體的東西添加事件,而通過給它的父輩添加事件,當我點擊那個具體的元素時,父輩的事件會觸發(因為有事件冒泡),而這時就需要用到evnet.target了,因為在父輩的事件中this并不指向當前點擊的那個元素,但是event.target可以獲取到是誰觸發的當前事件。以下是一個示例

  <ul id="ul">
    <li>111</li>
    <li>222</li>
    <li>333</li>
  </ul>
  <script>
    var ul = document.getElementById("ul");
    ul.onclick = function(event){
      console.log(event.target);
    };
  </script>

當我點擊第二個li時,輸出如下值

當然我們也可以直接給這三個li添加事件,但是那樣的話就綁定了3次事件,如果有1萬個元素,就綁定了1萬次,而通過事件委派則只需要綁定一次。

最主要倒也不是說不能給li添加事件,而是如果這些li并不是事先添加的,而是通過后端返回的數據,再渲染的,那么要是我們再放回數據之前就給li添加事件,那么就會有問題,因為根本就不存在li元素,也就是說后添加的元素無法事先去添加事件,比如下面這段代碼就有些問題。

  <ul id="ul">
    <li>1111</li>
  </ul>
  <script>
    var ul = document.getElementById("ul");
    var lis = ul.children;
    for(var i=0;i<lis.length;i++){
      lis[i].onclick = function(){
        console.log(this);
      };
    }
    
    var li = document.createElement("li");
    li.innerText = "2222";
    ul.appendChild(li);
  </script>

當我點擊第二個li時,什么都沒有輸出,如下圖

因為第二個li是在for循環以后添加的,所有并沒有給第二個li添加上事件。而如果是給ui添加事件,那就不一樣了,代碼如下

  <ul id="ul">
    <li>1111</li>
  </ul>
  <script>
    var ul = document.getElementById("ul");
    ul.onclick = function(event){
      console.log(event.target);
    };

    var li = document.createElement("li");
    li.innerText = "2222";
    ul.appendChild(li);
  </script>

不管我點擊第幾個li都可以正常的輸出,如下圖

不過因為event.target獲取到的是最終觸發這個事件的元素,所以在寫代碼的時候,我們經常需要加上判斷,因為通過event.target獲取到的不一定是我們想要的元素,比如下面這個例子

  <ul id="ul">
    <li>
      <em>111</em>
    </li>
  </ul>
  <script>
    var ul = document.getElementById("ul");
    ul.onclick = function(event){
      console.log(event.target);
    };
  </script>

當我點擊111的時候,獲取到的是em標簽,如下圖

但我想要的是li,因此我們就得加上判斷,我經常使用的一招就是,通過判斷元素的標簽名,代碼如下

  <ul id="ul">
    <li>
      <em>111</em>
    </li>
  </ul>
  <script>
    var ul = document.getElementById("ul");
    ul.onclick = function(event){
      var target = event.target;
      if(!(target.tagName.toLowerCase()==="li")){
        target = target.parentNode;
      }
      if(target.tagName.toLowerCase()==="li"){
        console.log(target);
      }
    };
  </script>

tagName可以獲取到元素名,但是每個瀏覽器獲取到的都有可能不同,有些瀏覽器獲取到的是大寫的標簽名,有些瀏覽器獲取到的是小寫的標簽名,因此在上面那段代碼中,將標簽名都轉換成小寫的,通過判斷標簽名來確定是不是我要的元素。

雖然這種判斷可行,但仔細想想也能想到,這個方法,也是有缺陷的,如果DOM比較復雜,則容易判斷錯誤,目前還沒有想到更好的方法。

記住正是因為有了事件捕獲和事件冒泡才有了event.target的用武之處。

relatedTarget

在event中有一個relatedTarget屬性,它可以獲取到和它相關的元素(通過誰來到這個元素上的,要到哪個元素上去),舉個例子

  <div id="box">
    <p>新的起點,新的夢想。</p>
  </div>
  <script>
    var box = document.getElementById("box");

    box.onmouseout = function(event){
      console.log(event.relatedTarget);
    };
  </script>

在onmouseout中relatedTarget可以獲取到它要到哪個元素上去,相反在onmouseover中relatedTarget獲取到的是從哪個元素來的,代碼如下

  <div id="box">
    <p>新的起點,新的夢想。</p>
  </div>
  <script>
    var box = document.getElementById("box");

    box.onmouseover = function(event){
      console.log(event.relatedTarget);
    };
  </script>

可以看到當我從html移入到div上時,relatedTarget獲取到的是html,也就是它從html來到div的。

想起一句話:我從哪里來,又要到哪里去。

當第一次看到這個屬性的時候,給我的感覺是,它肯定是很有用的,事實上也確實有些用處,如果細心的朋友,看上面的那個動畫圖,會發現一件事,onmouseover和onmouseout存在一個問題,離開或進入它的子元素事件也會被觸發。大多數情況我們是不希望這樣的,因此如果使用onmouseover或onmouseout時,最好判斷一下,是否真的移出了盒子。

在元素中有一個contains方法,可以用來判斷某個元素是否是它的子元素,如果是返回true,否則返回false,而以上問題我們就可以通過這個方法來寫,代碼如下

  <div id="box">
    <p>1111111
      <em>新的起點,新的夢想。</em>
    </p>
  </div>
  <script>
    var box = document.getElementById("box");

    box.onmouseout = function(event){
      if(!this.contains(event.relatedTarget)){
        console.log(this);
      }
    };
  </script>

如果你只是想解決以上這個問題,那么大可不必這樣寫,因為在瀏覽器中,分別有兩個和它們相同的事件,但它們并沒有這個問題,分別是onmouseenter鼠標移入事件和onmouseleave鼠標移出事件。

需要注意的是relatedTarget屬性只對onmouseout和onmouseover事件有用,雖然對onmouseenter和onmouseleave事件也有用,不過很少有人會那么去用,因為我發現relatedTarget屬性除了用在解決上面那個問題以外,還真沒發現有什么其他的用處。

注意點

需要注意一點,在普通函數中是不存在event對象的,只有在事件中才有,比如這么這段代碼,如果使用event就會輸出undefined。

(function(event){
  console.log(event); //undefined
})();

自定義事件

官方提供了一些如onclick、onmouseover、onscroll等事件給我們使用,但難免也會有自己建立事件的需求,比如監聽某個變量的變化,雖然聽起來好像不太可能,但方法總是有的,我們不讓使用者直接操作某個變量,而是提供一個方法給他操作,下面是一段實現思路。

  <script>
    var Foo = (function(){
      var a = 10;
      return {
        get:function(){
          return a;
        },
        set:function(value){
          if(a!==value){
            a = value;
            this.change();
          }
        },
        change:function(){
          console.log("a的值有變化");
        }
      };
    })();
    
    Foo.set(9);
    Foo.set(52);
  </script>

以上我將變量a封死在函數作用域里面,讓外部無法直接操作變量a,要操作就只能通過調用我事先設置的set方法,之所以要這樣弄是因為我們是無法知道變量是什么時候改變了的,而如果讓使用者操作我們提供事先提供的方法,那么我們就可以對其進行處理了。

以上并不是一個自定義事件,如果想要實現自定義方法,我們可以通過javascript提供的3個方法來弄,分別是document.createEvent()、event.initEvent()、element.dispatchEvent()。

  • createEvent用來創建一個新的事件
  • initEvent用來初始化事件
  • dispatchEvent用來觸發事件

具體可以看 http://www.w3school.com.cn/xmldom/met_event_initevent.asp

實現如下

  <script>
    var ev = document.createEvent("HTMLEvents");
    ev.initEvent("changeA",false,false);

    var Foo = (function(){
      var a = 10;
      return {
        get:function(){
          return a;
        },
        set:function(value){
          if(a!==value){
            a = value;
            document.dispatchEvent(ev);
          }
        }
      };
    })();
    
    document.addEventListener("changeA",function(){
      console.log("a有變化",Foo.get());
    });

    Foo.set(555);
    Foo.set(520);

  </script>

以上也就是將set中的change方法改成dispatchEvent,用來觸發changeA事件,如果你想要解綁這個事件,和你給onclick事件解綁是一樣的。

IE attachEvent之坑

當我們通過attachEvent添加事件時,需要注意一件事,我們為它添加的事件函數,IE并沒有將這個函數作為元素的方法調用,而是作為全局函數來調用,因此,它里面的this并不指向當前綁定的事件對象,而是window,并且event也不指向當前事件對象,看下面這段代碼

  <a href="javascript:;" id="a">addEventListener</a>
  <script>
    var a = document.getElementById("a");
    a.attachEvent("onclick",function(){
      console.log("this:",this);
      console.log("currentTarget:",event.currentTarget);
    });
  </script>

當我點擊a標簽時,輸出如下:


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 AutoPoster 的頭像
    AutoPoster

    互聯網 - 大數據

    AutoPoster 發表在 痞客邦 留言(0) 人氣()