看懂此文,不再困惑於 JS 中的事件設計

来源:http://www.cnblogs.com/springJournal/archive/2016/08/07/5745260.html
-Advertisement-
Play Games

看懂此文,不再困惑於 JS 中的事件設計 今天剛在關註的微信公眾號看到的文章,關於JS事件的,寫的很詳細也很容易理解,相關的知識點都有總結到,看完就有種很舒暢的感覺,該串起來的知識點都串起來了。反正一位元組:爽。 作者:aitangyong 鏈接:blog.csdn.net/aitangyong/ar ...


看懂此文,不再困惑於 JS 中的事件設計

今天剛在關註的微信公眾號看到的文章,關於JS事件的,寫的很詳細也很容易理解,相關的知識點都有總結到,看完就有種很舒暢的感覺,該串起來的知識點都串起來了。反正一位元組:爽。

 

作者:aitangyong

鏈接:blog.csdn.net/aitangyong/article/details/43231111

 

抽空學習了下javascript和jquery的事件設計,收穫頗大,總結此貼,和大家分享。

 

(一)事件綁定的幾種方式

 

javascript給DOM綁定事件處理函數總的來說有2種方式:在html文檔中綁定、在js代碼中綁定。下麵的方式1、方式2屬於在html中綁定事件,方式3、方式4和方式5屬於在js代碼中綁定事件,其中方法5是最推薦的做法。

方式1:

 

HTML的DOM元素支持onclick、onblur等以on開頭屬性,我們可以直接在這些屬性值中編寫javascript代碼。當點擊div的時候,下麵的代碼會彈出div的ID:

 

<div id="outestA" onclick="var id = this.id;alert(id);return false;"></div>  

 

這種做法很顯然不好,因為代碼都是放在字元串里的,不能格式化和排版,當代碼很多的時候很難看懂。這裡有一點值得說明:onclick屬性中的this代表的是當前被點擊的DOM對象,所以我們可以通過this.id獲取DOM元素的id屬性值。

 

方式2:

 

當代碼比較多的時候,我們可以在onclick等屬性中指定函數名。

 

跟上面的做法相比,這種做法略好一些。值得一提的是:事件處理函數中的this代表的是window對象,所以我們在onclick屬性值中,通過this將dom對象作為參數傳遞。

 

<script>

 

function buttonHandler(thisDom)

{

alert(this.id);//undefined

alert(thisDom.id);//outestA

return false;

}

</script>

<div id="outestA" onclick="return buttonHandler(this);"></div>

 

方式3:在JS代碼中通過dom元素的onclick等屬性

 

var dom = document.getElementById("outestA");

dom.onclick = function(){alert("1=" + this.id);};

dom.onclick = function(){alert("2=" + this.id);};

 

這種做法this代表當前的DOM對象。還有一點:這種做法只能綁定一個事件處理函數,後面的會覆蓋前面的。

 

方式4:IE下使用attachEvent/detachEvent函數進行事件綁定和取消。

 

attachEvent/detachEvent相容性不好,IE6~IE11都支持該函數,但是FF和Chrome瀏覽器都不支持該方法。而且attachEvent/detachEvent不是W3C標準的做法,所以不推薦使用。在IE瀏覽器下,attachEvent有以下特點。

 

a) 事件處理函數中this代表的是window對象,不是dom對象。

 

var dom = document.getElementById("outestA");  

dom.attachEvent('onclick',a);  

      

function a()  

{  

    alert(this.id);//undefined  

}

 

b) 同一個事件處理函數只能綁定一次。

 

var dom = document.getElementById("outestA");  

dom.attachEvent('onclick',a);  

dom.attachEvent('onclick',a);    

function a()  

{  

    alert(this.id);

}

 

雖然使用attachEvent綁定了2次,但是函數a只會調用一次。

 

c)不同的函數對象,可以重覆綁定,不會覆蓋。

 

var dom = document.getElementById("outestA");  

dom.attachEvent('onclick',function(){alert(1);});  

dom.attachEvent('onclick',function(){alert(1);});  

 

// 當outestA的click事件發生時,會彈出2個對話框

 

匿名函數和匿名函數是互相不相同的,即使代碼完全一樣。所以如果我們想用detachEvent取消attachEvent綁定的事件處理函數,那麼綁定事件的時候不能使用匿名函數,必須要將事件處事函數單獨寫成一個函數,否則無法取消。

 

方式5:使用W3C標準的addEventListener和removeEventListener。

 

這2個函數是W3C標準規定的,FF和Chrome瀏覽器都支持,IE6/IE7/IE8都不支持這2個函數。不過從IE9開始就支持了這2個標準的API。

 

// type:事件類型,不含"on",比如"click"、"mouseover"、"keydown";

// 而attachEvent的事件名稱,含含"on",比如"onclick"、"onmouseover"、"onkeydown";

// listener:事件處理函數

// useCapture是事件冒泡,還是事件捕獲,預設false,代表事件冒泡類型

addEventListener(type, listener, useCapture);

 

a) 事件處理函數中this代表的是dom對象,不是window,這個特性與attachEvent不同。

 

var dom = document.getElementById("outestA");  

dom.addEventListener('click', a, false);  

      

function a()  

{  

    alert(this.id);//outestA  

}

 

b) 同一個事件處理函數可以綁定2次,一次用於事件捕獲,一次用於事件冒泡。

 

var dom = document.getElementById("outestA");  

dom.addEventListener('click', a, false);  

dom.addEventListener('click', a, true);  

      

function a()  

{  

    alert(this.id);//outestA  

}

 

// 當點擊outestA的時候,函數a會調用2次

 

如果綁定的是同一個事件處理函數,並且都是事件冒泡類型或者事件捕獲類型,那麼只能綁定一次。

 

var dom = document.getElementById("outestA");  

dom.addEventListener('click', a, false);  

dom.addEventListener('click', a, false);  

      

function a()  

{  

    alert(this.id);//outestA  

}

 

// 當點擊outestA的時候,函數a只會調用1次

 

c) 不同的事件處理函數可以重覆綁定,這個特性與attachEvent一致。

 

(二)事件處理函數的執行順序

 

方式1、方式2和方式3都不能實現事件的重覆綁定,所以自然也就不存在執行順序的問題。方式4和方式5可以重覆綁定特性,所以需要瞭解下執行順序的問題。如果你寫出依賴於執行順序的代碼,可以斷定你的設計存在問題。所以下麵的順序問題,僅作為興趣探討,沒有什麼實際意義。直接上結論:addEventListener和attachEvent表現一致,如果給同一個事件綁定多個處理函數,先綁定的先執行。下麵的代碼我在IE11、FF17和Chrome39都測試過。

 

<script>

window.onload = function(){

<span style="white-space:pre"> </span>var outA = document.getElementById("outA");  

outA.addEventListener('click',function(){alert(1);},false);

outA.addEventListener('click',function(){alert(2);},true);

outA.addEventListener('click',function(){alert(3);},true);

outA.addEventListener('click',function(){alert(4);},true);

};

</script>

 

<body>

<div id="outA" style="width:400px; height:400px; background:#CDC9C9;position:relative;">

</div>

</body>

 

當點擊outA的時候,會依次列印出1、2、3、4。這裡特別需要註意:我們給outA綁定了多個onclick事件處理函數,也是直接點擊outA觸發的事件,所以不涉及事件冒泡和事件捕獲的問題,即addEventListener的第三個參數在這種場景下,沒有什麼用處。如果是通過事件冒泡或者是事件捕獲觸發outA的click事件,那麼函數的執行順序會有變化。

 

(三) 事件冒泡和事件捕獲

 

事件冒泡和事件捕獲很好理解,只不過是對同一件事情的不同看法,只不過這2種看法都很有道理。

我們知道HTML中的元素是可以嵌套的,形成類似於樹的層次關係。比如下麵的代碼:

 

<div id="outA" style="width:400px; height:400px; background:#CDC9C9;position:relative;">

<div id="outB" style="height:200; background:#0000ff;top:100px;position:relative;">

<div id="outC" style="height:100px; background:#FFB90F;top:50px;position:relative;"></div>

</div>

</div>

 

如果點擊了最內側的outC,那麼外側的outB和outC算不算被點擊了呢?很顯然算,不然就沒有必要區分事件冒泡和事件捕獲了,這一點各個瀏覽器廠家也沒有什麼疑義。假如outA、outB、outC都註冊了click類型事件處理函數,當點擊outC的時候,觸發順序是A–>B–>C,還是C–>B–>A呢?如果瀏覽器採用的是事件冒泡,那麼觸發順序是C–>B–>A,由內而外,像氣泡一樣,從水底浮向水面;如果採用的是事件捕獲,那麼觸發順序是A–>B–>C,從上到下,像石頭一樣,從水面落入水底。

 

事件冒泡見下圖:

 

 

事件捕獲見下圖:

 

 

一般來說事件冒泡機制,用的更多一些,所以在IE8以及之前,IE只支持事件冒泡。IE9+/FF/Chrome這2種模型都支持,可以通過addEventListener((type, listener, useCapture)的useCapture來設定,useCapture=false代表著事件冒泡,useCapture=true代表著採用事件捕獲。

 

<script>

 

window.onload = function(){

var outA = document.getElementById("outA");  

var outB = document.getElementById("outB");  

var outC = document.getElementById("outC");  

 

// 使用事件冒泡

outA.addEventListener('click',function(){alert(1);},false);

outB.addEventListener('click',function(){alert(2);},false);

outC.addEventListener('click',function(){alert(3);},false);

};

 

</script>

 

<body>

<div id="outA" style="width:400px; height:400px; background:#CDC9C9;position:relative;">

<div id="outB" style="height:200; background:#0000ff;top:100px;position:relative;">

<div id="outC" style="height:100px; background:#FFB90F;top:50px;position:relative;"></div>

</div>

</div>

</body>

 

使用的是事件冒泡,當點擊outC的時候,列印順序是3–>2–>1。如果將false改成true使用事件捕獲,列印順序是1–>2–>3。

 

(四) DOM事件流

 

DOM事件流我也不知道怎麼解釋,個人感覺就是事件冒泡和事件捕獲的結合體,直接看圖吧。

 

 

DOM事件流:將事件分為三個階段:捕獲階段、目標階段、冒泡階段。先調用捕獲階段的處理函數,其次調用目標階段的處理函數,最後調用冒泡階段的處理函數。這個過程很類似於Struts2框中的action和Interceptor。當發出一個URL請求的時候,先調用前置攔截器,其次調用action,最後調用後置攔截器。

 

<script>

 

window.onload = function(){

var outA = document.getElementById("outA");  

var outB = document.getElementById("outB");  

var outC = document.getElementById("outC");  

 

// 目標(自身觸發事件,是冒泡還是捕獲無所謂)

outC.addEventListener('click',function(){alert("target");},true);

 

// 事件冒泡

outA.addEventListener('click',function(){alert("bubble1");},false);

outB.addEventListener('click',function(){alert("bubble2");},false);

 

// 事件捕獲

outA.addEventListener('click',function(){alert("capture1");},true);

outB.addEventListener('click',function(){alert("capture2");},true);

};

 

</script>

 

<body>

<div id="outA" style="width:400px; height:400px; background:#CDC9C9;position:relative;">

<div id="outB" style="height:200; background:#0000ff;top:100px;position:relative;">

<div id="outC" style="height:100px; background:#FFB90F;top:50px;position:relative;"></div>

</div>

</div>

</body>

 

當點擊outC的時候,依次列印出capture1–>capture2–>target–>bubble2–>bubble1。到這裡是不是可以理解addEventListener(type,handler,useCapture)這個API中第三個參數useCapture的含義呢?useCapture=false意味著:將事件處理函數加入到冒泡階段,在冒泡階段會被調用;useCapture=true意味著:將事件處理函數加入到捕獲階段,在捕獲階段會被調用。從DOM事件流模型可以看出,捕獲階段的事件處理函數,一定比冒泡階段的事件處理函數先執行。

 

(五) 再談事件函數執行先後順序

 

在DOM事件流中提到過:

 

// 目標(自身觸發事件,是冒泡還是捕獲無所謂)

outC.addEventListener('click',function(){alert("target");},true);

 

我們在outC上觸發onclick事件(這個是目標對象),如果我們在outC上同時綁定捕獲階段/冒泡階段事件處理函數會怎麼樣呢?

 

<script>

 

window.onload = function(){

var outA = document.getElementById("outA");  

var outB = document.getElementById("outB");  

var outC = document.getElementById("outC");  

 

// 目標(自身觸發事件,是冒泡還是捕獲無所謂)

outC.addEventListener('click',function(){alert("target2");},true);

outC.addEventListener('click',function(){alert("target1");},true);

 

// 事件冒泡

outA.addEventListener('click',function(){alert("bubble1");},false);

outB.addEventListener('click',function(){alert("bubble2");},false);

 

// 事件捕獲

outA.addEventListener('click',function(){alert("capture1");},true);

outB.addEventListener('click',function(){alert("capture2");},true);

 

 

 

};

 

</script>

 

<body>

<div id="outA" style="width:400px; height:400px; background:#CDC9C9;position:relative;">

<div id="outB" style="height:200; background:#0000ff;top:100px;position:relative;">

<div id="outC" style="height:100px; background:#FFB90F;top:50px;position:relative;"></div>

</div>

</div>

</body>

 

點擊outC的時候,列印順序是:capture1–>capture2–>target2–>target1–>bubble2–>bubble1。由於outC是我們觸發事件的目標對象,在outC上註冊的事件處理函數,屬於DOM事件流中的目標階段。目標階段函數的執行順序:先註冊的先執行,後註冊的後執行。這就是上面我們說的,在目標對象上綁定的函數是採用捕獲,還是採用冒泡,都沒有什麼關係,因為冒泡和捕獲只是對父元素上的函數執行順序有影響,對自己沒有什麼影響。如果不信,可以將下麵的代碼放進去驗證。

 

// 目標(自身觸發事件,是冒泡還是捕獲無所謂)

outC.addEventListener('click',function(){alert("target1");},false);

outC.addEventListener('click',function(){alert("target2");},true);

outC.addEventListener('click',function(){alert("target3");},true);

outC.addEventListener('click',function(){alert("target4");},false);

 

至此我們可以給出事件函數執行順序的結論了:捕獲階段的處理函數最先執行,其次是目標階段的處理函數,最後是冒泡階段的處理函數。目標階段的處理函數,先註冊的先執行,後註冊的後執行。

 

(六) 阻止事件冒泡和捕獲

 

預設情況下,多個事件處理函數會按照DOM事件流模型中的順序執行。如果子元素上發生某個事件,不需要執行父元素上註冊的事件處理函數,那麼我們可以停止捕獲和冒泡,避免沒有意義的函數調用。前面提到的5種事件綁定方式,都可以實現阻止事件的傳播。由於第5種方式,是最推薦的做法。所以我們基於第5種方式,看看如何阻止事件的傳播行為。IE8以及以前可以通過 window.event.cancelBubble=true阻止事件的繼續傳播;IE9+/FF/Chrome通過event.stopPropagation()阻止事件的繼續傳播。

 

<script>

 

window.onload = function(){

var outA = document.getElementById("outA");  

var outB = document.getElementById("outB");  

var outC = document.getElementById("outC");  

 

// 目標

outC.addEventListener('click',function(event){

alert("target");

event.stopPropagation();

},false);

 

// 事件冒泡

outA.addEventListener('click',function(){alert("bubble");},false);

 

// 事件捕獲

outA.addEventListener('click',function(){alert("capture");},true);

 

};

 

</script>

 

<body>

<div id="outA" style="width:400px; height:400px; background:#CDC9C9;position:relative;">

<div id="outB" style="height:200; background:#0000ff;top:100px;position:relative;">

<div id="outC" style="height:100px; background:#FFB90F;top:50px;position:relative;"></div>

</div>

</div>

</body>

 

當點擊outC的時候,之後列印出capture–>target,不會列印出bubble。因為當事件傳播到outC上的處理函數時,通過stopPropagation阻止了事件的繼續傳播,所以不會繼續傳播到冒泡階段。

 

最後再看一段更有意思的代碼:

 

<script>

 

window.onload = function(){

var outA = document.getElementById("outA");  

var outB = document.getElementById("outB");  

var outC = document.getElementById("outC");  

 

// 目標

outC.addEventListener('click',function(event){alert("target");},false);

 

// 事件冒泡

outA.addEventListener('click',function(){alert("bubble");},false);

 

// 事件捕獲

outA.addEventListener('click',function(){alert("capture");event.stopPropagation();},true);

 

};

 

</script>

 

<body>

<div id="outA" style="width:400px; height:400px; background:#CDC9C9;position:relative;">

<div id="outB" style="height:200; background:#0000ff;top:100px;position:relative;">

<div id="outC" style="height:100px; background:#FFB90F;top:50px;position:relative;"></div>

</div>

</div>

</body>

 

執行結果是只列印capture,不會列印target和bubble。神奇吧,我們點擊了outC,但是卻沒有觸發outC上的事件處理函數,而是觸發了outA上的事件處理函數。原因不做解釋,如果你還不明白,可以再讀一遍本文章。

 

 

 


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

-Advertisement-
Play Games
更多相關文章
  • 持久化(Persistence) 即把數據(如記憶體中的對象)保存到可永久保存的存儲設備中(如磁碟)。持久化的主要應用是將記憶體中的對象存儲在關係型的資料庫中,當然也可以存儲在磁碟文件中、XML數據文件中等等。 持久化是將程式數據在持久狀態和瞬時狀態間轉換的機制。 JDBC就是一種持久化機制。文件IO也 ...
  • //fourth day to study python 24. In python , how to create funcation. we can use def to define funcation. such as: def MyFirstFuncation(): print('this ...
  • 請註意以下要點: 1、是否開啟了認證,QQ郵箱、163郵箱均要開啟認證 2、javax.mail.MessagingException: Could not connect to SMTP host: smtp.163.com, port: 25; //連接超時 解決參考:將這個屬性的true加上引 ...
  • stdafx.h: Form1.h ...
  • 1.安裝 Erlang,官網:https://www.erlang.org/ 2.安裝RabbitMQ伺服器,rabbitMQ server,官網http://www.rabbitmq.com/ 註:可以根據不同的需要到官網進行相關下載!!! 3.安裝完成之後需要對其進行配置變數: 創建新的系統變數 ...
  • 最近想用C++在windows下實現一個基本的圖像查看器功能,目前只想到了使用GDI或OpenGL兩種方式。由於實在不想用GDI的API了,就用OpenGL的方式實現了一下基本的顯示功能。用GDAL讀取圖像,這樣就能與圖像格式無關。OpenGL的glDrawPixels()函數也能實現圖像顯示,但是... ...
  • 數組的定義: JavaScript 中的數組是一種特殊的對象,用來表示偏移量的索引是該對象的屬性,索引可能是整數。然而,這些數字索引在內部被轉換為字元串類型,這是因為 JavaScript 對象中的屬性名必須是字元串。在內部被歸類為數組。由於 Array 在 JavaScript 中被當作對象,因此 ...
  • 快速排序,又稱劃分交換排序。以分治法為策略實現的快速排序演算法。 本文主要要談的是利用javascript實現in-place思想的快速排序 分治法: 在電腦科學中,分治法是建基於多項分支遞歸的一種很重要的演算法範式。字面上的解釋是“分而治之”,就是把一個複雜的問題分成兩個或更多的相同或相似的子問題, ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...