事件

事件

JavaScript和HTML的交互是通過事件實(shí)現(xiàn)的。JavaScript采用異步事件驅(qū)動(dòng)編程模型,當(dāng)文檔、瀏覽器、元素或與之相關(guān)對(duì)象發(fā)生特定事情時(shí),瀏覽器會(huì)產(chǎn)生事件。如果JavaScript關(guān)注特定類型事件,那么它可以注冊(cè)當(dāng)這類事件發(fā)生時(shí)要調(diào)用的句柄。事件是某個(gè)行為或者觸發(fā),比如點(diǎn)擊、鼠標(biāo)移動(dòng).....

  • 當(dāng)用戶點(diǎn)擊鼠標(biāo)時(shí)
  • 當(dāng)網(wǎng)頁已加載時(shí)
  • 當(dāng)圖像已加載時(shí)
  • 當(dāng)鼠標(biāo)移動(dòng)到元素上時(shí)
  • 當(dāng)用戶觸發(fā)按鍵時(shí)...

事件流

事件流描述的是從頁面中接收事件的順序,比如有兩個(gè)嵌套的div,點(diǎn)擊了內(nèi)層的div,這時(shí)候是內(nèi)層的div先觸發(fā)click事件還是外層先觸發(fā)?目前主要有三種模型:

  1. 事件冒泡:事件開始時(shí)由最具體的元素接收,然后逐級(jí)向上傳播到較為不具體的元素

  2. 事件捕獲:不太具體的節(jié)點(diǎn)更早接收事件,而最具體的元素最后接收事件,和事件冒泡相反

  3. DOM事件流:DOM2級(jí)事件規(guī)定事件流包括三個(gè)階段,事件捕獲階段,處于目標(biāo)階段,事件冒泡階段,首先發(fā)生的是事件捕獲,為截取事件提供機(jī)會(huì),然后是實(shí)際目標(biāo)接收事件,最后是冒泡階段

image.png

事件處理程序

我們也稱之為事件偵聽器(listener),事件就是用戶或?yàn)g覽器自身執(zhí)行的某種動(dòng)作。比如click、load、mouseover等,都是事件類型(俗稱事件名稱),而響應(yīng)某個(gè)事件的方法就叫做事件處理程序或者事件監(jiān)聽器。

也就是我們需要提前定義好某些事件發(fā)生了該怎么處理,這個(gè)過程叫做綁定事件處理程序。

給元素添加事件處理程序

方法一:HTML內(nèi)聯(lián)方式

元素支持的每個(gè)事件都可以使用一個(gè)相應(yīng)事件處理程序同名的HTML屬性指定。這個(gè)屬性的值應(yīng)該是可以執(zhí)行的JavaScript代碼,我們可以為一個(gè)button添加click事件處理程序

<input type="button" value="Click Here" onclick="alert('Clicked!');" />

在HTML事件處理程序中可以包含要執(zhí)行的具體動(dòng)作,也可以調(diào)用在頁面其它地方定義的腳本,剛才的例子可以寫成這樣

<input type="button" value="Click Here" onclick="showMessage();" />

在HTML中指定事件處理程序書寫很方便,但是有兩個(gè)缺點(diǎn):

  1. 存在加載順序問題,如果事件處理程序在html代碼之后加載,用戶可能在事件處理程序還未加載完成時(shí)就點(diǎn)擊按鈕之類的觸發(fā)事件,存在時(shí)間差問題

  2. 這樣書寫html代碼和JavaScript代碼緊密耦合,維護(hù)不方便

方法二:JavaScript指定事件處理程序

通過JavaScript指定事件處理程序就是把一個(gè)方法賦值給一個(gè)元素的事件處理程序?qū)傩浴?/p>

每個(gè)元素都有自己的事件處理程序?qū)傩?,這些屬性名稱通常為小寫,如onclick等,將這些屬性的值設(shè)置為一個(gè)函數(shù),就可以指定事件處理程序,如下

<input id="btnClick" type="button" value="Click Here" />

<script type="text/javascript">
    var btnClick = document.getElementById('btnClick');
    btnClick.onclick = function showMessage() {
        alert(this.id);
    };
</script>

這樣處理,事件處理程序被認(rèn)為是元素的方法,事件處理程序在元素的作用域下運(yùn)行,this就是當(dāng)前元素,所以點(diǎn)擊button結(jié)果是:btnClick

這樣還有一個(gè)好處,我們可以刪除事件處理程序,只需把元素的onclick屬性賦為null即可。

事件對(duì)象

在觸發(fā)DOM上的某個(gè)事件的時(shí)候會(huì)產(chǎn)生一個(gè)事件對(duì)象event,這個(gè)對(duì)象包含著所有與事件有關(guān)的信息,包括產(chǎn)生事件的元素、事件類型等相關(guān)信息。所有瀏覽器都支持event對(duì)象,但支持方式不同。

DOM中的事件對(duì)象

兼容DOM的瀏覽器會(huì)產(chǎn)生一個(gè)event對(duì)象傳入事件處理程序中。event對(duì)象包含與創(chuàng)建它的特定事件有關(guān)的屬性和方法,觸發(fā)事件的類型不同,可用的屬性和方法也不同,但是所有事件都會(huì)包含

image.png

在事件處理程序內(nèi)部,this始終等同于currentTarget,而target是事件的實(shí)際目標(biāo)。

要阻止事件的默認(rèn)行為,可以使用preventDefault()方法,前提是cancelable值為true,比如我們可以阻止鏈接導(dǎo)航這一默認(rèn)行為

document.querySelector('#btn').onclick = function (e) {
    e.preventDefault();
}

stopPropagation()方法可以停止事件在DOM層次的傳播,即取消進(jìn)一步的事件捕獲或冒泡。我們可以在button的事件處理程序中調(diào)用stopPropagation()從而避免注冊(cè)在body上的事件發(fā)生

var handler = function (e) {
    alert(e.type);
    e.stopPropagation();
}

addEvent(document.body, 'click', function () { alert('Clicked body')});
var btnClick = document.getElementById('btnClick');
addEvent(btnClick, 'click', handler);

若是注釋掉e.stopPropagation(); 在點(diǎn)擊button的時(shí)候,由于事件冒泡,body的click事件也會(huì)觸發(fā),但是調(diào)用這句后,事件會(huì)停止傳播

IE中的事件對(duì)象

訪問IE中的event對(duì)象有幾種不同的方式,取決于指定事件處理程序的方法。直接為DOM元素添加事件處理程序時(shí),event對(duì)象作為window對(duì)象的一個(gè)屬性存在

var handler = function () {
    var e = window.event;
    alert(e.type);
}
var btnClick = document.getElementById('btnClick');
btnClick.onclick = handler;

我們通過window.event取得了event對(duì)象,并檢測(cè)到了其類型,可是如果事件處理程序是通過attachEvent添加的,那么就會(huì)有一個(gè)event對(duì)象被傳入事件處理程序中

var handler = function (e) {
    alert(e.type);
}
var btnClick = document.getElementById('btnClick');
attachEvent(btnClick, handler);

當(dāng)然這時(shí)候也可以通過window對(duì)象訪問event,方便起見,我們一般會(huì)傳入event對(duì)象,IE中所有的事件都包含以下屬性方法

image.png

跨瀏覽器的事件對(duì)象

雖然DOM和IE的event對(duì)象不同,但基于它們的相似性,我們還是可以寫出跨瀏覽器的事件對(duì)象方案

function getEvent(e) {
    return e || window.event;
}

function getTarget(e) {
    return e.target || e.scrElement;
}

function preventDefault(e) {
    if (e.preventDefault)
        e.preventDefault();
    else
        e.returnValue = false;
}

function stopPropagation(e) {
    if (e.stopPropagation)
        e.stopPropagation();
    else
        e.cancelBubble = true;
}

1: DOM0 事件和DOM2級(jí)在事件監(jiān)聽使用方式上有什么區(qū)別?

在W3C對(duì)DOM事件進(jìn)行規(guī)范化之前的事件處理,一般稱為DOM0級(jí)事件處理程序。W3C在DOM2級(jí)文檔規(guī)范中,包含了DOM 事件(DOM Events)規(guī)范,也就是我們通常所說的DOM2級(jí)事件處理程序。

DOM0級(jí)事件處理方式:

  • Dom0級(jí)事件處理程序是將一個(gè)函數(shù)賦值給一個(gè)事件處理程序?qū)傩裕ㄟ^將事件處理程序設(shè)置為null刪除綁定在元素上的事件處理程序。這種方法無法給一個(gè)事件添加多個(gè)事件處理程序,一個(gè)事件只能綁定一次,后面的程序會(huì)覆蓋前面的程序。
// 添加事件處理程序
var btn=document.querySelector("#btn");
btn.onclick=function () {
    alert(this.id);
}
// 刪除事件處理程序
btn.onclick=null;

優(yōu)點(diǎn): 簡(jiǎn)單,兼容性好。
缺點(diǎn): 一個(gè)事件處理程序只能對(duì)應(yīng)一個(gè)處理函數(shù),設(shè)置第二個(gè)事件時(shí)候,因?yàn)槭琴x值,所以第二個(gè)事件會(huì)覆蓋第一個(gè)事件。

DOM2級(jí)事件處理方式:

  • DOM2級(jí)事件處理方式指定了,添加事件處理程序和刪除事件處理程序的方法。DOM2級(jí)定義了addEventListener()和removeEventListener()用于處理指定和刪除事件處理程序。所有Dom節(jié)點(diǎn)都包含這兩個(gè)方法,并且它們都接受3個(gè)參數(shù),要處理的事件名、作為事件處理程序的函數(shù)和一個(gè)布爾值。最后這個(gè)布爾值參數(shù)如果是true,表示在捕獲階段調(diào)用事件處理程序;如果是false,表示在冒泡階段調(diào)用事件處理程序。
  addEventListener(eventName,func,isPuhuo);     //添加事件
  removeEventListener(eventName,func,isPuhuo);  //刪除事件
/*參數(shù)分別是,事件處理屬性名稱,處理函數(shù),是否在捕獲時(shí)執(zhí)行事件處理函數(shù),
默認(rèn)是false,即在冒泡階段執(zhí)行
*/

應(yīng)用舉例

  var btn = document.getElementById("btn");
  var cancel = document.getElementById("cancel")
  function fn(){
    console.log("I am clicked 123");
  }
  btn.addEventListener("click",fn)  //點(diǎn)擊btn事件,執(zhí)行函數(shù)fn
  cancel.addEventListener("click",function(){
    btn.removeEventListener("click",fn)
  })  //點(diǎn)擊cancel事件,執(zhí)行刪除btn的綁定事件

通過removeEventListener()函數(shù)來移除事件處理程序時(shí),移除時(shí)的參數(shù)必須與添加處理程序時(shí)使用的參數(shù)相同,這也意味著通過addEventListener()添加的匿名函數(shù)將無法移除。如:

var btn=document.querySelector("#btn");
// 指定事件處理程序
btn.addEventListener("click",function(){
    alert(this.id);
},false);
// 刪除事件處理程序,不會(huì)生效
btn.removeEventListener("click",function(){
    alert(this.id);
},false);

注:

(a) eventName的值均不含on,例如注冊(cè)鼠標(biāo)點(diǎn)擊事件eventName為“click”

(b) 處理函數(shù)中的this依然指的是指當(dāng)前dom元素

(c) 通過addEventListener添加的事件處理程序,只能通過removeEventListener來刪除,也就是說通過addEventListener添加的匿名函數(shù)將無法被刪除。

二者區(qū)別:使用Dom2級(jí)方法添加事件處理程序的主要好處是可以添加多個(gè)事件處理程序,而Dom0級(jí)為一個(gè)事件添加多個(gè)事件處理程序時(shí),后面的程序會(huì)覆蓋前面的。

2: attachEvent與addEventListener的區(qū)別?

  1. 參數(shù)個(gè)數(shù)不相同,這個(gè)最直觀,addEventListener有三個(gè)參數(shù),attachEvent只有兩個(gè),attachEvent添加的事件處理程序只能發(fā)生在冒泡階段,addEventListener第三個(gè)參數(shù)可以決定添加的事件處理程序是在捕獲階段還是冒泡階段處理(我們一般為了瀏覽器兼容性都設(shè)置為冒泡階段)

  2. 第一個(gè)參數(shù)意義不同,addEventListener第一個(gè)參數(shù)是事件類型(比如click,load),而attachEvent第一個(gè)參數(shù)指明的是事件處理函數(shù)名稱(onclick,onload)

  3. 事件處理程序的作用域不相同,addEventListener的作用域是元素本身,this是指的觸發(fā)元素,而attachEvent事件處理程序會(huì)在全局變量?jī)?nèi)運(yùn)行,this是window,所以剛才例子才會(huì)返回undefined,而不是元素id

  4. 為一個(gè)事件添加多個(gè)事件處理程序時(shí),執(zhí)行順序不同,addEventListener添加會(huì)按照添加順序執(zhí)行,而attachEvent添加多個(gè)事件處理程序時(shí)順序無規(guī)律(添加的方法少的時(shí)候大多是按添加順序的反順序執(zhí)行的,但是添加的多了就無規(guī)律了),所以添加多個(gè)的時(shí)候,不依賴執(zhí)行順序的還好,若是依賴于函數(shù)執(zhí)行順序,最好自己處理,不要指望瀏覽器。

3: 解釋IE事件冒泡和DOM2事件傳播機(jī)制?

IE事件冒泡:

  • 事件從目標(biāo)元素向父級(jí)元素傳遞,直到傳遞到 window ( document) 停止。
image.png

DOM2級(jí)事件傳播:有3個(gè)階段:

  • 捕獲階段:事件從document向子元素一層層傳遞,直到目標(biāo)元素
  • 處于目標(biāo)階段:此時(shí)事件發(fā)生在目標(biāo)元素上,被看做冒泡的一部分

  • 冒泡階段:和IE冒泡一樣,從目標(biāo)元素向父級(jí)元素傳遞,直到document停止。

image.png

4:如何阻止事件冒泡? 如何阻止默認(rèn)事件?

兼容DOM的瀏覽器:

  • 阻止默認(rèn)事件:e.preventDefault();

event.preventDefault():如果事件可取消,則取消該事件,而不停止事件的進(jìn)一步傳播。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>event.preventDefault()</title>
</head>
<body>
    <p>請(qǐng)點(diǎn)擊復(fù)選框控件</p>
    <form>
        <label for="id-checkbox">Checkbox</label>
        <input type="checkbox" id="id-checkbox" name="checkbox" />
    </form>
    <script>
        document.querySelector("#id-checkbox").addEventListener("click", function(event){
            alert("preventDefault會(huì)阻止該復(fù)選框被勾選.")
            event.preventDefault();
            //阻止該復(fù)選框被勾選
        }, false);
    </script>
</body>
</html>
  • 阻止事件冒泡: e.stopPropagation();

event.stopPropagation(): 阻止捕獲和冒泡階段中當(dāng)前事件的進(jìn)一步傳播。

<!DOCTYPE html>
<html>
<head>
<title>Event Propagation</title>

<style type="text/css">
 #t-daddy { border: 1px solid red }
 #c1 { background-color: pink; }
</style>

<script type="text/javascript">

function stopEvent(ev) {
  c2 = document.getElementById("c2");
  c2.innerHTML = "hello";

  // this ought to keep t-daddy from getting the click.
  ev.stopPropagation();
  alert("event propagation halted.");
}

function load() {
  elem = document.getElementById("tbl1");
  elem.addEventListener("click", stopEvent, false);
}
</script>
</head>

<body onload="load();">

<table id="t-daddy" onclick="alert('hi');">
 <tr id="tbl1">
  <td id="c1">one</td>
 </tr>
 <tr>
  <td id="c2">two</td>
 </tr>
</table>

</body>
</html>

IE9 之前的IE瀏覽器:

  • 阻止默認(rèn)冒泡:e.cancelBubble = true;

  • 阻止默認(rèn)事件:event.returnValue = false;

5:有如下代碼,要求當(dāng)點(diǎn)擊每一個(gè)元素li時(shí)控制臺(tái)展示該元素的文本內(nèi)容。不考慮兼容

<ul class="ct">
    <li>這里是</li>
    <li>秦皇島</li>
    <li>東北大學(xué)秦皇島分校</li>
</ul>
<script>
//todo ...
</script>
----------------------------------------------------------
<ul class="ct">
    <li>這里是</li>
    <li>秦皇島</li>
    <li>東北大學(xué)秦皇島分校</li>
</ul>
<script>
var ct = document.querySelector('.ct');
ct.addEventListener('click',function(e){
      console.log(e.target.innerText)
});
</script>

6: 補(bǔ)全代碼要求:(1)當(dāng)點(diǎn)擊按鈕開頭添加時(shí)在<li>這里是</li>元素前添加一個(gè)新元素,內(nèi)容為用戶輸入的非空字符串;當(dāng)點(diǎn)擊結(jié)尾添加時(shí)在最后一個(gè) li 元素后添加用戶輸入的非空字符串;(2)當(dāng)點(diǎn)擊每一個(gè)元素li時(shí)控制臺(tái)展示該元素的文本內(nèi)容。

<ul class="ct">
    <li>這里是</li>
    <li>秦皇島</li>
    <li>東北大學(xué)秦皇島分校</li>
</ul>
<input class="ipt-add-content" placeholder="添加內(nèi)容"/>
<button id="btn-add-start">開頭添加</button>
<button id="btn-add-end">結(jié)尾添加</button>
<script>
//你的代碼
</script>
---------------------------------------------------------------------
<ul class="ct">
    <li>這里是</li>
    <li>秦皇島</li>
    <li>東北大學(xué)秦皇島分校</li>
</ul>
<input class="ipt-add-content" placeholder="添加內(nèi)容"/>
<button id="btn-add-start">開頭添加</button>
<button id="btn-add-end">結(jié)尾添加</button>
<script>
var ct = document.querySelector('.ct'),
      ipt = document.querySelector('.ipt-add-content'),
      addStart = document.querySelector('#btn-add-start'),
      addEnd = document.querySelector('#btn-add-end');
  

ct.addEventListener('click',function(e)){
    if(e.target.tagName.toLowerCase() === 'li'){
        console.log(e.target.innerText);
    }
});

addStart.addEventListener('click',function(){
    var li = document.createElement('li');
    li.innerText = ipt.value;
    if (li.innerText === "") {
            alert("please input content");
        } else {
            ct.inserBefore(li,ct.firstChild);
        }
});

addEnd.addEventListener('click',function(){
    var li = document.createElement('li');
    li.innerText = ipt.value;
    if (li.innerText === "") {
            alert("please input content")
        } else {
            ct.appendChild(li);
        }
})
</script>

7: 補(bǔ)全代碼,要求:當(dāng)鼠標(biāo)放置在li元素上,會(huì)在img-preview里展示當(dāng)前l(fā)i元素的data-img對(duì)應(yīng)的圖片。

<ul class="ct">
    <li data-img="1.png">鼠標(biāo)放置查看圖片1</li>
    <li data-img="2.png">鼠標(biāo)放置查看圖片2</li>
    <li data-img="3.png">鼠標(biāo)放置查看圖片3</li>
</ul>
<div class="img-preview"></div>
<script>
//你的代碼
</script>
-------------------------------------------------------------
<body>
    <ul class="ct">
        <li data-img="http://cdn.jirengu.com/book.jirengu.com/img/11.jpg">鼠標(biāo)放置查看圖片1</li>
        <li data-img="http://cdn.jirengu.com/book.jirengu.com/img/13.jpg">鼠標(biāo)放置查看圖片2</li>
        <li data-img="http://cdn.jirengu.com/book.jirengu.com/img/14.jpg">鼠標(biāo)放置查看圖片3</li>
    </ul>
    <div class="img-preview"></div>

    <script>
        function $(id) {
            return document.querySelector(id);
        }

        function $$(cls) {
            return document.querySelectorAll(cls);
        }

        var ct = $('.ct')
        var childs = $$('li')
        var preview = $('.img-preview')

//直接在每一個(gè)元素上監(jiān)聽
        for (var i = 0; i < childs.length; i++) {
            childs[i].addEventListener('mouseenter', function () {
                var dataImg = this.getAttribute('data-img');
                preview.innerHTML = '![](' + dataImg + ')'
            })
        }

//監(jiān)聽父元素
        ct.addEventListener('mouseover', function (e) {
            if (e.target.tagName.toLowerCase() === 'li') {
                var img = document.createElement('img');
                img.src = e.target.getAttribute('data-img');
                preview.appendChild(img);
            }
        })

        ct.addEventListener('mouseout', function (e) {
            if (e.target.tagName.toLowerCase() === 'li') {
                preview.innerHTML = '';
            }
        });
      
    </script>
</body>
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • JavaScript 程序采用了異步事件驅(qū)動(dòng)編程模型。在這種程序設(shè)計(jì)風(fēng)格下,當(dāng)文檔、瀏覽器、元素或與之相關(guān)的對(duì)象發(fā)...
    劼哥stone閱讀 1,278評(píng)論 3 11
  • 聲明:本文來源于http://www.webzsky.com/?p=731我只是在這里作為自己的學(xué)習(xí)筆記整理一下(...
    angryyan閱讀 7,121評(píng)論 1 6
  • 一、問答 1. dom對(duì)象的innerText和innerHTML有什么區(qū)別? innerHTML: 也就是從對(duì)象...
    饑人谷_羅偉恩閱讀 642評(píng)論 0 2
  • 如何批量操作 css 如何獲取 DOM 計(jì)算后的樣式 使用getComputedStyle獲取元素計(jì)算后的樣式 實(shí)...
    _Dot912閱讀 580評(píng)論 1 3
  • 以下文章為轉(zhuǎn)載,對(duì)理解JavaScript中的事件處理機(jī)制很有幫助,淺顯易懂,特分享于此。 什么是事件? 事件(E...
    jxyjxy閱讀 3,069評(píng)論 1 10