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

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

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

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

事件流

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

事件冒泡

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

image

理論及運(yùn)用

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

運(yùn)用事件委托 更詳細(xì)的介紹看這篇文章

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

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

事件委托實(shí)例

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

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

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

代碼: 代碼運(yùn)行

//事件委托
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  
  // 判斷是否匹配目標(biāo)元素
  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ù)場(chǎng)景需要阻止冒泡事件的冒泡,比如在一個(gè)HTML結(jié)構(gòu)中,父級(jí)包含著子級(jí),當(dāng)事件在子級(jí)發(fā)生時(shí)(click,mouseenter,mouseleave等),由于事件冒泡就會(huì)觸發(fā)父級(jí)的同名事件。示例:

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

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

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

應(yīng)用

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

事件捕獲

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

image

DOM事件流

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

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

image

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

事件處理程序

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

HTML事件處理程序

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

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

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

缺點(diǎn)

首先,存在一個(gè)時(shí)差問題。因?yàn)橛脩艨赡軙?huì)在 HTML 元素一出現(xiàn)在頁(yè)面上就觸發(fā)相應(yīng)的事件,但當(dāng)時(shí)的事件處理程序有可能尚不具備執(zhí)行條件。

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

最后一個(gè)缺點(diǎn)是 HTML 與 JavaScript 代碼緊密耦合。如果要更換事 件處理程序,就要改動(dòng)兩個(gè)地方:HTML 代碼和 JavaScript 代碼。

DOM0 級(jí)事件處理程序

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

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

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

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

DOM2 級(jí)事件處理程序

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

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

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

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

上面的代碼為一個(gè)按鈕添加了 onclick 事件處理程序,而且該事件會(huì)在冒泡階段被觸發(fā)(因?yàn)樽詈笠粋€(gè)參數(shù)是 false,此時(shí)和DOM0級(jí)的onclick事件差不多)。

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

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

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

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

沿著DOM樹向上冒泡的事件不會(huì)觸發(fā)被指定為為true的listener(因?yàn)榇藭r(shí)為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>該實(shí)例演示了在添加事件監(jiān)聽時(shí)冒泡與捕獲階段的不同。</p>
<div id="parent" class="parent">
    <div id="child" class="child">點(diǎn)擊該方塊, 我是冒泡</div>
</div><br>
<div id="parent2" class="parent">
    <div id="child2" class="child">點(diǎn)擊該方塊, 我是捕獲</div>
</div>
<script>
//事件冒泡    child的DOM事件先發(fā)生(粉),parent的DOM事件再發(fā)生(橙)。
document.getElementById("child").addEventListener("click", function(){
    alert("你點(diǎn)擊了 粉色!");
}, false);
document.getElementById("parent").addEventListener("click", function(){
    alert("你點(diǎn)擊了 橙色!");
}, false);

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

點(diǎn)擊查看實(shí)例效果

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

事件對(duì)象

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

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 級(jí)事件”規(guī)定了以下幾類事件。

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

內(nèi)存和性能

事件委托

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

移除事件處理程序

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

在兩種情況下,可能會(huì)造成上述問題。第一種情況就是從文檔中移除帶有事件處理程序的元素時(shí)。 這可能是通過純粹的 DOM 操作,例如使用 removeChild()和 replaceChild()方法。但更多地是發(fā) 生在使用 innerHTML 替換頁(yè)面中某一部分的時(shí)候。如果帶有事件處理程序的元素被 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>

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

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

模擬事件

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

模擬鼠標(biāo)事件

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

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

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

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

實(shí)例運(yùn)用 代碼運(yùn)行

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

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

模擬鍵盤事件

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

模擬其他事件

自定義 DOM 事件

實(shí)例運(yùn)用 代碼運(yùn)行

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

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

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

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

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

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