[夯實前端基礎(chǔ)]--js事件詳解

tips:歡迎關(guān)注我在github的博客點擊查看 。

遇到j(luò)s事件的問題思考,回去補當(dāng)初自己的總結(jié)文,受益匪淺,感謝當(dāng)初的自己??原文地址

javaScript 與 HTML 之間的交互是通過事件實現(xiàn)的。

事件流

概念:事件流描述的是從頁面中接收事件的順序。

事件冒泡

概念:即事件開始時由最具體的元素(文檔中嵌套層次最深 的那個節(jié)點)接收,然后逐級向上傳播到較為不具體的節(jié)點(文檔)。

image

理論及運用

  • 任何可以冒泡的事件都不僅僅可以在事件目標上進行處理,目標的任何祖先節(jié)點上也能處理。

運用事件委托 更詳細的介紹看這篇文章

事件委托原理:以將事件處理程序附加到更高層的地方負責(zé)多個目標的事件處理。

大多數(shù) Web 應(yīng)用在用戶交互上大量用到事件處理程序。頁面上的事件處理程序的數(shù)量和頁面響應(yīng) 用戶交互的速度之間有個負相關(guān)。為了減輕這種懲罰,最好使用事件代理。

事件委托實例

<ul id="list">  
        <li>我是孩子1</li>  
        <li>我是孩子2</li>  
        <li>我是孩子3</li>  
        <li>我是孩子4</li>  
        <li>我是孩子5</li>  
</ul> 
<div id = "btn">添加一個新孩子</div>

場景1:需要在點擊列表項的時候響應(yīng)一個事件。如果給每個列表項一一都綁定一個函數(shù),那對于內(nèi)存消耗是非常大的,效率上需要消耗很多性能。因此,比較好的方法就是把這個點擊事件綁定到他的父層,也就是 ul 上,然后在執(zhí)行事件的時候再去匹配判斷目標元素;

場景2:動態(tài)添加新的Li元素。在很多時候,我們需要通過 AJAX 或者用戶操作動態(tài)的增加或者去除列表項元素,那么在每一次改變的時候都需要重新給新增的元素綁定事件,給即將刪去的元素解綁事件。如果用了事件委托就沒有這種麻煩了,因為事件是綁定在父層的,和目標元素的增減是沒有關(guān)系的,執(zhí)行到目標元素是在真正響應(yīng)執(zhí)行事件函數(shù)的過程中去匹配的;

代碼: 代碼運行

//事件委托
var ul = document.getElementById('list'); 
ul.addEventListener('click', function (event) {
  // 兼容性處理
  var event = event||window.event;
  var target = event.target || event.srcElement;//target表示在事件冒泡中觸發(fā)事件的源元素,在IE中是srcElement  
  // 判斷是否匹配目標元素
  if (target.nodeName.toLowerCase() == 'li') {
       alert(target.innerHTML);
  }
});
//添加li元素
document.getElementById('btn').addEventListener('click', function (event) {
        var li = document.createElement('li');  
        li.innerHTML="我是新孩子";  
        ul.appendChild(li);  
})
  • 不是所有的事件都能冒泡。以下事件不冒泡:blur、focus、load、unload。

  • 有些業(yè)務(wù)場景需要阻止冒泡事件的冒泡,比如在一個HTML結(jié)構(gòu)中,父級包含著子級,當(dāng)事件在子級發(fā)生時(click,mouseenter,mouseleave等),由于事件冒泡就會觸發(fā)父級的同名事件。示例:

<ul id="parent">
    <li id="child">son</li>
</ul>
   child.addEventListener("click",function(event){
        alert(1);
        // event stopPropagation(); //標準瀏覽器
        //或者是
        //event cancelBubble = true; //IE
    })
    parent.addEventListener("click",function(event){
        alert(1);
    })

當(dāng)點擊li時,會彈出兩個1,可以通過阻止冒泡防止這樣的行為,點擊li就彈出一個1。
event.stopPropagation()或event.cancelBubble = true 可以阻止事件冒泡。

  • 阻止冒泡并不能阻止對象默認行為。比如submit按鈕被點擊后會提交表單數(shù)據(jù),這種行為無須我們寫程序定制。

應(yīng)用

  • 事件代理,事件冒泡允許多個操作被集中處理(把事件處理器添加到一個父級元素上,避免把事件處理器添加到多個子級元素上),它還可以讓你在對象層的不同級別捕獲事件
  • 讓不同的對象同時捕獲同一事件,并調(diào)用自己的專屬處理程序做自己的事情,就像老板一下命令,各自員工做自己崗位上的工作去了。

事件捕獲

概念:事件捕獲的思想 是不太具體的節(jié)點應(yīng)該更早接收到事件,而最具體的節(jié)點應(yīng)該最后接收到事件。事件捕獲的用意在于在 事件到達預(yù)定目標之前捕獲它。

image

DOM事件流

“DOM2級事件”規(guī)定的事件流包括三個階段::事件捕獲階段、處于目標階段和事件冒泡階段。

首先發(fā)生的是事件捕獲,為截獲事件提供了機會。然后是實際的目標接收到事件。最后一個階段是冒泡階 段,可以在這個階段對事件做出響應(yīng)。

image

在 DOM 事件流中,實際的目標(<div>元素)在捕獲階段不會接收到事件。這意味著在捕獲階段, 事件從 document 到<html>再到<body>后就停止了。下一個階段是“處于目標”階段,于是事件在<div> 上發(fā)生,并在事件處理(后面將會討論這個概念)中被看成冒泡階段的一部分。然后,冒泡階段發(fā)生, 事件又傳播回文檔。

事件處理程序

概念:事件就是用戶或瀏覽器自身執(zhí)行的某種動作。諸如 click、load 和 mouseover,都是事件的名字。 而響應(yīng)某個事件的函數(shù)就叫做事件處理程序(或事件偵聽器)。事件處理程序的名字以"on"開頭,onclick等。

HTML事件處理程序

概念:某個元素支持的每種事件,都可以使用一個與相應(yīng)事件處理程序同名的 HTML 特性來指定。

例如,要在按鈕被單擊時執(zhí)行一些 JavaScript,可以像下面 這樣編寫代碼:

<input type="button" value="Click Me" onclick="alert(&quot;Clicked&quot;)" />

缺點

首先,存在一個時差問題。因為用戶可能會在 HTML 元素一出現(xiàn)在頁面上就觸發(fā)相應(yīng)的事件,但當(dāng)時的事件處理程序有可能尚不具備執(zhí)行條件。

另一個缺點是,這樣擴展事件處理程序的作用域鏈在不同瀏覽器中會導(dǎo)致不同結(jié)果。

最后一個缺點是 HTML 與 JavaScript 代碼緊密耦合。如果要更換事 件處理程序,就要改動兩個地方:HTML 代碼和 JavaScript 代碼。

DOM0 級事件處理程序

簡單來說就是取得一 個要操作的對象的引用,然后為它指定了某事件處理程序(如onclick)。使用 DOM0 級方法指定的事件處理程序被認為是元素的方法,以這種方式添加的事件處理程序會在事件流的冒泡階段被處理。

var btn = document.getElementById("myBtn"); 
btn.onclick = function(){ 
    alert(this.id); //"myBtn" 
};

btn.onclick = null;//刪除事件處理程序

將事件處理程序設(shè)置為 null 之后,再單擊按鈕將不會有任何動作發(fā)生。

DOM2 級事件處理程序

“DOM2 級事件”定義了兩個方法,用于處理指定和刪除事件處理程序的操作:addEventListener() 和 removeEventListener()。

onclick 事件會被覆蓋,addevenlistener 事件先后執(zhí)行。

所有 DOM 節(jié)點中都包含這兩個方法,并且它們都接受 3 個參數(shù):要處理的事件名、作為事件處理程序的函數(shù)和一個布爾值。最后這個布爾值參數(shù)如果是 true,表示在捕獲 階段調(diào)用事件處理程序;如果是 false,表示在冒泡階段調(diào)用事件處理程序。沒有指定的話,默認為 false。

target.addEventListener(type, listener, useCapture);
var btn = document.getElementById("myBtn"); 
btn.addEventListener("click", function(){ 
    alert(this.id); 
}, false);

上面的代碼為一個按鈕添加了 onclick 事件處理程序,而且該事件會在冒泡階段被觸發(fā)(因為最后一個參數(shù)是 false,此時和DOM0級的onclick事件差不多)。

使用 DOM2 級方法添加事件處理程序的主要好處是可以添加多個事件處理程序。

var btn = document.getElementById("myBtn");
btn.addEventListener("click", function(){
    alert(this.id); 
}, false); 
btn.addEventListener("click", function(){
    alert("Hello world!"); 
}, false);

這兩個事件處理程序會按照添加它們的順序觸發(fā),因此首先會顯示元素的 ID,其次會顯示"Hello world!"消息。

MDN 對第三個參數(shù)的解釋是:Boolean,是指在DOM樹中,注冊了該listener的元素,是否會先于它下方的任何事件目標,接收到該事件。

沿著DOM樹向上冒泡的事件不會觸發(fā)被指定為為true的listener(因為此時為true是比冒泡更早的事件捕獲階段)。

例子: (引自csdn的一篇文章

image.png
<style>
.parent
{
    position: relative;
    background-color: coral;
    border: 1px solid;
    padding: 50px;
}
.child {
    position: relative;
    background-color: pink;
    width: 100px;
    height: 100px;
}
</style>
<body>
<p>該實例演示了在添加事件監(jiān)聽時冒泡與捕獲階段的不同。</p>
<div id="parent" class="parent">
    <div id="child" class="child">點擊該方塊, 我是冒泡</div>
</div><br>
<div id="parent2" class="parent">
    <div id="child2" class="child">點擊該方塊, 我是捕獲</div>
</div>
<script>
//事件冒泡    child的DOM事件先發(fā)生(粉),parent的DOM事件再發(fā)生(橙)。
document.getElementById("child").addEventListener("click", function(){
    alert("你點擊了 粉色!");
}, false);
document.getElementById("parent").addEventListener("click", function(){
    alert("你點擊了 橙色!");
}, false);

//事件捕獲  parent的DOM事件先發(fā)生(橙),parent的DOM事件再發(fā)生(粉)。
document.getElementById("child2").addEventListener("click", function(){
    alert("你點擊了 粉色!");
}, true);
document.getElementById("parent2").addEventListener("click", function(){
    alert("你點擊了 橙色!");
}, true);
</script>
</body>

點擊查看實例效果

大多數(shù)情況下,都是將事件處理程序添加到事件流的冒泡階段,這樣可以最大限度地兼容各種瀏覽器。最好只在需要在事件到達目標之前截獲它的時候?qū)⑹录幚沓绦蛱砑拥讲东@階段。如果不是特別需 要,我們不建議在事件捕獲階段注冊事件處理程序。

事件對象

兼容 DOM 的瀏覽器會將一個 event 對象傳入到事件處理程序中。無論指定事件處理程序時使用什 么方法(DOM0 級或 DOM2 級),都會傳入 event 對象。

var btn = document.getElementById("myBtn"); 
btn.onclick = function(event){
    alert(event.type); //"click" 
}; 
btn.addEventListener("click", function(event){
alert(event.type); //"click" 
}, false);

觸發(fā)的事件類型不一樣,可用的屬性和方法也不一樣。常見的有event.type,event.target 等

事件類型

Web 瀏覽器中可能發(fā)生的事件有很多類型。如前所述,不同的事件類型具有不同的信息,而“DOM3 級事件”規(guī)定了以下幾類事件。

  • UI(User Interface,用戶界面)事件,當(dāng)用戶與頁面上的元素交互時觸發(fā); 如resize,scroll
  • 焦點事件,當(dāng)元素獲得或失去焦點時觸發(fā); 如blur (在元素失去焦點時觸發(fā),這個事件不會冒泡)
  • 鼠標事件,當(dāng)用戶通過鼠標在頁面上執(zhí)行操作時觸發(fā);如click
  • 滾輪事件,當(dāng)使用鼠標滾輪(或類似設(shè)備)時觸發(fā);
  • 文本事件,當(dāng)在文檔中輸入文本時觸發(fā);
  • 鍵盤事件,當(dāng)用戶通過鍵盤在頁面上執(zhí)行操作時觸發(fā);
  • 合成事件,當(dāng)為 IME(Input Method Editor,輸入法編輯器)輸入字符時觸發(fā);
  • 變動(mutation)事件,當(dāng)?shù)讓?DOM 結(jié)構(gòu)發(fā)生變化時觸發(fā)。

內(nèi)存和性能

事件委托

這個前面講到了 ,這邊不重復(fù)。

移除事件處理程序

每當(dāng)將事件處理程序指定給元素時,運行中的瀏覽器代碼與支持頁面交互的 JavaScript 代碼之間就 會建立一個連接。。這種連接越多,頁面執(zhí)行起來就越慢。如前所述,可以采用事件委托技術(shù),限制建立 的連接數(shù)量。另外,在不需要的時候移除事件處理程序,也是解決這個問題的一種方案。內(nèi)存中留有那 些過時不用的“空事件處理程序”(dangling event handler),也是造成 Web 應(yīng)用程序內(nèi)存與性能問題的 主要原因。

在兩種情況下,可能會造成上述問題。第一種情況就是從文檔中移除帶有事件處理程序的元素時。 這可能是通過純粹的 DOM 操作,例如使用 removeChild()和 replaceChild()方法。但更多地是發(fā) 生在使用 innerHTML 替換頁面中某一部分的時候。如果帶有事件處理程序的元素被 innerHTML 刪除 了,那么原來添加到元素中的事件處理程序極有可能無法被當(dāng)作垃圾回收。

<div id="myDiv"> 
    <input type="button" value="Click Me" id="myBtn"> 
</div>
<script type="text/javascript">
    var btn = document.getElementById("myBtn");
    btn.onclick = function(){
    //先執(zhí)行某些操作
   btn.onclick = null; //移除事件處理程序
    document.getElementById("myDiv").innerHTML = "處理中..."; //把按鈕替換成文字
}; 
</script>

這里,有一個按鈕被包含在<div>元素中。為避免雙擊,單擊這個按鈕時就將按鈕移除并替換成一 條消息;這是網(wǎng)站設(shè)計中非常流行的一種做法。但問題在于,當(dāng)按鈕被從頁面中移除時,它還帶著一個 事件處理程序呢。在<div>元素上設(shè)置 innerHTML 可以把按鈕移走,但事件處理程序仍然與按鈕保持 著引用關(guān)系。有的瀏覽器(尤其是 IE)在這種情況下不會作出恰當(dāng)?shù)靥幚?,它們很有可能會將對元素?對事件處理程序的引用都保存在內(nèi)存中。如果你知道某個元素即將被移除,那么最好手工移除事件處理 程序

第二種情況,就是卸載頁面的時候。如果在頁面被卸載之前沒 有清理干凈事件處理程序,那它們就會滯留在內(nèi)存中。每次加載完頁面再卸載頁面時(可能是在兩個頁 面間來回切換,也可以是單擊了“刷新”按鈕),內(nèi)存中滯留的對象數(shù)目就會增加,因為事件處理程序 占用的內(nèi)存并沒有被釋放。一般來說,最好的做法是在頁面卸載之前,先通過 onunload 事件處理程序移除所有事件處理程序。 在此,事件委托技術(shù)再次表現(xiàn)出它的優(yōu)勢——需要跟蹤的事件處理程序越少,移除它們就越容易。對這 種類似撤銷的操作,我們可以把它想象成:只要是通過 onload 事件處理程序添加的東西,最后都要通 過 onunload 事件處理程序?qū)⑺鼈円瞥?/p>

模擬事件

概念:使用 JavaScript 在任意時刻來觸發(fā)特定的事件,此時的事件就如同瀏覽器創(chuàng)建的事件一樣。

模擬鼠標事件

思路: 1.創(chuàng)建事件對象 2.分發(fā)事件

          //創(chuàng)建事件對象
           var event = new MouseEvent('click', {
                cancelable: true,
                bubble: true,
                view: document.defaultView
           });
           //分發(fā)事件 
          document.getElementById("myBtn").dispatchEvent(event);

ps:紅包書介紹的創(chuàng)建事件對象的方法有點過時了,現(xiàn)被MDN不建議使用

    var event = document.createEvent("MouseEvents");  //創(chuàng)建事件對象 
    event.initMouseEvent("click", true, true, document.defaultView, 0, 0, 0, 0, 0, false, false, false, false, 0, null);  //初始化事件對象 
image

實例運用 代碼運行

這邊有兩個按鈕,按鈕1綁定一個鼠標點擊事件1,alert出一句話;在按鈕2綁定一個鼠標點擊事件2,鼠標點擊事件2將模擬用戶去點擊按鈕1。所以點按鈕2,觸發(fā)一個模擬鼠標事件去點擊按鈕1。

<body>
    <button id="btn1">我是按鈕1</button>
    <button id="btn2">我是按鈕2,點我一下 按鈕1也會被點擊哦</button>
    <script type="text/javascript">
        var btn=document.getElementById("#btn1");
        // 按鈕1的點擊事件
        btn.addEventListener('click',function(e){
           console.log("我是按鈕1,我被點擊啦") // 點擊按鈕2,按鈕1也會被點擊(模擬事件);只點擊按鈕1(正常事件)
        })
        // 按鈕2的點擊事件
        document.querySelector("#btn2").addEventListener('click',function(e){
           //模擬鼠標事件步奏1  創(chuàng)建事件對象
           var event = new MouseEvent('click', {
                cancelable: true,
                bubble: true,
                view: document.defaultView
           });
           //模擬鼠標事件步奏2  觸發(fā)事件 
          btn.dispatchEvent(event);
   })
      </script>
</body>

模擬鍵盤事件

這邊舉一反三,不做解釋。

模擬其他事件

自定義 DOM 事件

實例運用 代碼運行

<body>
    <button id="btn1">我是按鈕1,我沒鼠標事件,點我不會輸出</button>
    <button id="btn2">我是按鈕2,點我一下會執(zhí)行綁定在按鈕1的自定義事件,會有輸出</button>
    <script type="text/javascript">
       var btn= document.querySelector("#btn1");
        // 按鈕1的點擊事件
        btn.addEventListener('test',function(e){
           console.log("我是按鈕1,我被點擊啦") // 點擊按鈕2,有輸出(自定義事件);只點擊按鈕1,沒輸出(自定義事件,非模擬鼠標事件)
        })
       // 按鈕2的點擊事件
        document.querySelector("#btn2").addEventListener('click',function(e){
          //自定義事件步奏1  創(chuàng)建事件對象
           var event = new Event('test', {
                cancelable: true,
                bubble: true,
                view: document.defaultView
           });
           //自定義鼠標事件步奏2  觸發(fā)事件 
          btn.dispatchEvent(event);
   })
      </script>
</body>

紅寶書的方法有點過時,新實現(xiàn)方法可看這篇文章

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • ??JavaScript 與 HTML 之間的交互是通過事件實現(xiàn)的。 ??事件,就是文檔或瀏覽器窗口中發(fā)生的一些特...
    霜天曉閱讀 3,715評論 1 11
  • JavaScript 與 HTML 之間的交互是通過事件實現(xiàn)的。事件,就是文檔或瀏覽器窗口中發(fā)生的一些特定的交互瞬...
    LemonnYan閱讀 745評論 0 4
  • 事件流 JavaScript與HTML之間的交互是通過事件實現(xiàn)的。事件,就是文檔或瀏覽器窗口中發(fā)生的一些特定的交互...
    DHFE閱讀 917評論 0 3
  • JavaScript 程序采用了異步事件驅(qū)動編程模型。在這種程序設(shè)計風(fēng)格下,當(dāng)文檔、瀏覽器、元素或與之相關(guān)的對象發(fā)...
    劼哥stone閱讀 1,335評論 3 11
  • JavaScript 與 HTML 之間的交互是通過事件實現(xiàn)的。事件,就是文檔或瀏覽器窗口中發(fā)生的一些特定的交互瞬...
    threetowns閱讀 417評論 0 0

友情鏈接更多精彩內(nèi)容