走進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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...