走進javascript——DOM事件

来源:http://www.cnblogs.com/pssp/archive/2017/02/17/6382874.html
-Advertisement-
Play Games

DOM事件模型 在0級DOM事件模型中,它只是簡單的執行你為它綁定的事件,比如你為某個元素添加了一個onclick事件,當事件觸發時,它只是去調用我們綁定的那個方法,不再做其他的操作。 在2級DOM事件模型中,就比較複雜一些,它將不再是單純的調用一下自身綁定的事件就完事了,它還擁有機會去處理它的祖先 ...


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標簽時,輸出如下:


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Struts2.xml 本篇博客主要講struts.xml中package下的標簽和標簽屬性,主要分以下四個部分說明: (1)action的配置基本屬性 (2)同一個Action類中不同方法滿足不同的action邏輯 (3)通配符解決多業務問題 (4)配置處理結果: (1)action的配置基本屬性 ...
  • 為了更好的學習設計模式,以及督促自己完成設計模式的學習,現提筆為記。 怎麼的,每周至少也要學一個設計模式!!! 懇請大家的監督和不吝賜教,共同學習和進步! 內容主要參考自《設計模式之禪》以及相關網路博文! 源碼路徑: "源代碼" C GitHub 目錄 1. "想學設計模式,你得先會看類圖,一張圖讀 ...
  • 今天我們來講一下命令模式。 一、案例 我們去燒烤店吃燒烤,給我們烤羊肉串和雞翅。用簡單的控制台應用程式來模擬一下。 客戶端調用: 二、演繹 1、第一步演繹 如果燒烤店裡有好多人,都要了若幹的烤串和雞翅,那麼,烤肉串者怎麼記得誰點了什麼,點了多少串呢?這樣就會亂掉了。如何解決這個問題呢?我們需要服務員 ...
  • extends與implements的不同 1、在類的聲明中,通過關鍵字extends來創建一個類的子類。 一個類通過關鍵字implements聲明自己使用一個或者多個介面。 extends 是繼承某個類, 繼承之後可以使用父類的方法, 也可以重寫父類的方法; implements 是實現多個介面, ...
  • 今天我們來講一下橋接模式。 一、案例 我有N牌子的一個手機,需要運行一款游戲軟體。咱們用簡單的控制台應用程式來實現一下。 客戶端調用: 二、演繹 1、第一步演繹: 如果我不僅有N品牌的手機,還有M品牌的手機也需要運行這款游戲軟體,怎麼辦? 我們可以將運行游戲軟體抽象出一個父類,讓N,M品牌的手機繼承 ...
  • 很久沒寫博客了,因為最近在用react+express做一個自己的工具型網站(其實就是奪寶島搶拍器) 然後因為經常要改動,而且又要放到伺服器上進行測試。總是要webpack,然後手動把文件上傳上去,不勝其煩,索性搜索了下,直接寫個能檢測文件變化並自動進行上傳的腳本好了。 首先,我們使用npm 安裝兩 ...
  • 什麼是webpack webpack是一個模塊化載入器 支持AMD/CMD。 webpack優勢 代碼分割 Loaders 插件機制 ... 安裝webpack 全局安裝 局部安裝 安裝某個指定的版本 例如1.14.0 webpack命令行參數 p 編譯後會壓縮文件 w/ watch 開發環境下會監 ...
  • //先定義一個數組 anular代碼: var app = angular.module('serApp', []); app.controller('indexCtrl', function($scope, $http) { $scope.arrs = [{ n:'a'; arr:['1','2' ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...