【重溫基礎】20.事件

本文是 重溫基礎 系列文章的第二十篇。html

這是第三個基礎系列的第一篇,歡迎持續關注呀!
重溫基礎 系列的【初級】和【中級】的文章,已經統一整理到個人【Cute-JavaScript】的JavaScript基礎系列中。git

今日感覺:電影有時候看的是緣分。github

系列目錄:web

本章節複習的是JS中的事件,事件冒泡捕獲代理模擬等等。瀏覽器

前置知識:
JavaScript與HTML的交互式經過事件來實現的,是文檔或瀏覽器窗口中發生的一些特定的交互瞬間。閉包

1.事件流

事件流描述的是從頁面中接收事件的順序,一般有這樣兩種徹底相反的事件流概念:事件冒泡流(IE團隊提出)和事件捕獲流(網景團隊提出)。dom

1.1 事件冒泡

冒泡事件(Event Bubbling):事件開始時由最具體的元素接收(文檔中嵌套層次最深的那個節點),而後逐層向上傳播到較爲不具體的節點(文檔),看下示例代碼:svg

<!DOCTYPE html>
<html>
<head>
    <title>leo 事件冒泡</title>
</head>
<body>
    <div id="leo">點擊</div>
</body>
</html>

點擊頁面中<div>元素,這個click事件就會按照下面順序傳播:函數

  1. <div>
  2. <body>
  3. <html>
  4. document

因而可知,元素綁定的事件會經過DOM樹向上傳播,每層節點都會發生,直到document對象,如圖展現了冒泡過程:
事件冒泡流post

1.2 事件捕獲

事件捕獲(Event Capturing):讓不太具體的節點更早接收事件,而最具體的節點最後接收事件,即在事件到達預約目標以前捕獲到,看下示例代碼(HTML代碼和前面同樣),事件捕獲的過程是這樣的:

  1. document
  2. <html>
  3. <body>
  4. <div>

看得出,document對象最新接收事件,而後沿DOM樹依次向下,直到最後的實際目標<div>元素,如圖展現了捕獲過程:

事件捕獲流

注意:因爲老版本的瀏覽器不支持,所以不多人使用事件捕獲,不過若是特殊需求仍是可使用事件捕獲,建議仍是使用事件冒泡。

1.3 DOM事件流

「DOM2級事件」規定的事件流包含三個階段:事件捕獲階段處於目標階段事件冒泡階段
事件捕獲爲截獲事件提供機會,而後實際的目標接收到事件,最後事件冒泡,對事件做出響應。按照前面的HTML代碼,整個流程是這樣的:

DOM事件流

在DOM事件流中,實際目標(<div>元素)在捕獲階段不接收事件,即在捕獲階段,事件從document對象<html>再到<body>後就中止,進入「處於目標」階段,事件在<div>元素上發生,而後才進入冒泡階段,將事件傳回給文檔。

注意:目前主流瀏覽器都支持DOM事件流,只有IE8和以前版本不支持。

2.事件處理

事件處理,即響應某個事件。咱們把事件處理的函數,稱爲「事件處理程序」。
事件處理程序的名稱通常都以on開頭,如click事件的事件處理程序就是onclickload事件的事件處理程序就是onload
咱們將事件處理程序,分爲這麼幾類:

  • HTML事件處理程序
  • DOM0級事件處理程序
  • DOM2級事件處理程序
  • IE事件處理程序
  • 跨瀏覽器事件處理程序

2.1 HTML事件處理程序

某個元素支持的事件,均可以用一個與相應事件處理程序同名的HTML特性來指定,這個特性的值應該是可以執行的JavaScript代碼。好比:

<input type="button" value="點擊" onclick="alert('hello leo');">

也能夠把須要執行的具體事件單獨定義出來,能夠放置與單獨.js文件,也能夠在文檔內用<script>標籤引入:

function fun(){
    alert('hello leo');
}
<input type="button" value="點擊" onclick="fun()">

咱們經過這樣指定事件處理程序,能夠有一個局部變量event來獲取事件對象自己,在這個函數內部,this值等於這個變量event

<input type="button" value="點擊" onclick="fun(event)">

另外,HTML中指定事件處理程序,會有2個缺點:

  1. 存在時間差
    可能出現這樣的狀況:HTML元素觸發事件,可是事件處理程序還未定義(函數的定義在HTML最底下定義),就會出現報錯,這與HTML代碼加載順序有關。
  2. 做用域鏈的異常
    因爲不一樣瀏覽器JavaScript引擎遵循的標識符解析規則存在差別,致使訪問非限定對象成員時出錯,表現爲事件處理程序的做用域鏈在不一樣瀏覽器結果不一樣。
  3. HTML和JavaScript代碼緊密耦合
    這經常就是不少開發人員放棄HTML事件處理程序的緣由。

2.2 DOM0級事件處理程序

經過賦值形式,將一個函數賦值給一個事件處理程序屬性。每一個元素(包含windowdocument)都有本身的事件處理屬性,這些屬性一般所有小寫,如onclick,將這種屬性的值設置成一個函數,就能夠指定事件處理程序:

var leo = document.getElementById('leo');
leo.onclick = function(){
    alert('hello leo!');
}

使用DOM0級方法指定事件處理程序,被認爲是元素的方法。此時的事件處理程序是在元素的做用域執行,那麼,this就引用當前元素,能夠訪問元素的任何屬性和方法:

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

咱們也能夠經過設置事件處理程序屬性來刪除DOM0級的事件處理程序。

leo.onclick = null;

2.3 DOM2級事件處理程序

有2個方法:

  • 添加事件處理程序:addEventListener()
  • 刪除事件處理程序:removeEventListener()

全部的DOM節點都包含這兩個方法,而且它們都接收三個參數:

  • 處理的事件名稱
  • 事件處理程序的函數
  • 布爾值(true:事件捕獲階段調用,false:事件冒泡階段調用)
var leo = document.getElementById('leo');
leo.addEventListener('click',function(){
    alert(this.id);        // "leo"
},false);

與DOM0級方法同樣,這裏的事件處理程序也會是在元素的做用域中執行,所以this也是指向元素,能夠訪問元素的任何屬性和方法。

使用DOM2級方法,能夠添加多個事件處理程序,並按照添加順序觸發

var leo = document.getElementById('leo');
leo.addEventListener('click',function(){
    alert(this.id);       // "leo"
},false);
leo.addEventListener('click',function(){
    alert('hello leo!');  // "hello leo!"
},false);

注意:經過addEventListener()添加的事件只能經過removeEventListener()移除,而且二者傳入的參數一致,這就意味着經過addEventListener()添加的匿名函數不能被移除,看下面代碼:

var leo = document.getElementById('leo');
leo.addEventListener('click',function(){
    alert(this.id);       // "leo"
},false);

// 沒有效果
leo.removeEventListener('click',function(){
    // do some thing
},false);

沒有效果是由於這兩個方法傳入的函數,是徹底不一樣的,爲了達到刪除事件處理程序的效果,咱們能夠將處理函數單獨定義出來:

var leo = document.getElementById('leo');
var fun = function(){
    alert(this.id);
}
leo.addEventListener('click',fun,false);
// 有效果
leo.removeEventListener('click',fun,false);

2.4 IE事件處理程序

IE實現兩種方法: attachEvent()detachEvent()。這兩個方法接收相同的兩個參數:事件處理程序名稱事件處理程序函數
因爲IE8和更早版本只支持事件冒泡,因此經過attachEvent()添加的事件處理程序會被添加到冒泡階段。

var leo = document.getElementById('leo');
leo.attachEvent('onclick',function(){
    alert('hello leo');       // "leo"
},false);
// attachEvent也支持添加多個事件處理程序

leo.attachEvent('onclick',function(){
    alert('hello world');       // "leo"
},false);

注意:這裏的第一個參數是onclick而不是DOM的addEventListener()click

IE的attachEvent()和DOM0級方法區別
二者事件處理程序的做用域不一樣。

  • DOM0級方法,做用域在所屬元素的做用域。
  • attachEvent(),做用域在全局做用域,即this指向window

和DOM0級方法同樣,detachEvent()只能移除使用attachEvent()添加的方法,爲了不沒法移除,也是須要將處理的函數單獨定義出來:

var leo = document.getElementById('leo');
var fun = function(){
    alert(this.id);
}
leo.attachEvent('onclick',fun,false);
// 有效果
leo.detachEvent('onclick',fun,false);

2.6 跨瀏覽器事件處理程序

在作跨瀏覽器事件處理程序,咱們能夠有兩種方式:

  1. 使用可以隔離瀏覽器差別的JavaScript庫
  2. 單獨手寫一個處理方法

咱們單獨受寫一個處理方法也不難,只需關注好事件冒泡階段,咱們能夠建立一個方法,區分使用DOM0級方法DOM2級方法IE方法便可,默認採用DOM0級方法

var my_event = {
    addMyEvent:function(element, type, handler){
        if(element.addEventListener){
            element.addEventListener(type, handler, false);
        }else if(element.attachEvent){
            element.attachEvent('on' + type, handler);
        }else{
            element['on' + type] = handler;
        }
    };
    removeMyEvent:function(element, type, handler){
        if(element.removeEventListener){
            element.removeEventListener(type, handler, false);
        }else if(element.detachEvent){
            element.detachEvent('on' + type, handler);
        }else{
            element['on' + type] = null;
        }
    }
}

3.事件對象

當觸發一個DOM上的事件時,都會產生一個事件對象event,並做爲參數傳入事件處理程序,這個對象包含全部與事件相關的信息。包括致使事件的元素、事件類型等其餘信息。

3.1 DOM中的事件對象

不管指定事件處理程序時使用什麼方法(DOM0級方法或DOM2級方法),都會傳入event對象:

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

event對象包含與建立它的特定事件相關的屬性和方法,經常有以下成員:
event成員1

event成員2

咱們可使用event中的對象和方法:

  • 阻止事件的默認行爲
var leo = document.getElementById('leo');
leo.onclick = function(event){
    // 只有當 event 中的 cancelable 屬性爲true的事件
    event.preventDefault();
}
  • 當即中止事件在DOM的傳播

經過調用event.stopPropagation()方法避免彈框出現兩次。

var leo = document.getElementById('leo');
leo.onclick = function(event){
    alert('leo');
    event.stopPropagation();
}
document.body.onclick = function(event){
    alert('hello leo');
}

3.2 IE中的事件對象

訪問IE中的事件對象event,方法有多種,取決於事件處理程序的方法:

  • DOM0級方法,使用window.event
var leo = document.getElementById('leo');
leo.onclick = function(){
    var event = window.event;
    alert(event.type);   // 'click'
}
  • IE的attachEvent方法,event做爲參數傳入(也可使用window.event
var leo = document.getElementById('leo');
leo.attachEvent('onclick',function(event){
    alert(event.type);   // 'click'
},false);

3.3 跨瀏覽器的事件對象

雖然DOM和IE中event對象不一樣,但咱們也能夠和前面的 跨瀏覽器事件處理程序 處理同樣,經過他們之間的區別,實現映射:

var my_event = {
    myAddFun : function(element, type, handler){
        // do some thing
    },
    // 返回對event的引用
    getMyEvent : function(event){
        return event?event:window.event;
    },
    // 返回事件的目標
    getMyTarget : function(event){
        return event.target || event.srcElement;
    },
    // 取消事件的默認行爲
    preventDefault : function(event){
        if(event.preventDefault){
            event.preventDefault();
        }else {
            event.returnValue = false;
        }
    },

    myRemoveFun : function(element, type, handler){
        // do some thing
    },

    // 阻止事件流
    stopPropagetion : function(event){
        if(event.stopPropagetion){
            event.stopPropagetion();
        }else {
            event.cancelBubble = true;
        }
    }
}

var leo = document.getElementById('leo');
leo.onclick = function(event){
    alert('leo');
    event = my_event(event);
    my_event.stopPropagation(event);
}

leo.onclick = function(event){
    alert('hello world');
}

4.事件類型

Web瀏覽器中的事件類型有不少,DOM3級事件規定有如下幾類事件類型:

  • UI事件:當用戶與頁面上元素交互時觸發;
  • 焦點事件:當元素失去或獲取焦點時觸發;
  • 鼠標事件:當用戶經過鼠標在頁面操做時觸發;
  • 滾輪事件:當使用鼠標滾輪(或相似設備)時觸發;
  • 文本事件:當在文檔中輸入文本時觸發;
  • 鍵盤事件:當用戶經過鍵盤操做時觸發;
  • 合成事件:當爲IME輸入字符時觸發;
  • 變更事件:當底層DOM結構變更時觸發;

具體每一個方法的詳細介紹,能夠查看**W3school HTML DOM Event 對象**
或者查看《JavaScript高級程序設計(第三版)》的第362頁開始。
我後面會單獨整理一篇,介紹這些事件的文章。

5.事件委託

簡單理解就是講一個響應事件委託到另外一個元素。
事件委託利用事件冒泡,指定一個事件處理程序來管理某一類型的全部事件,好比咱們經過一個函數來代替其餘三個函數:

<ul id="btn">
    <li id="btn1">按鈕1</li>
    <li id="btn2">按鈕2</li>
    <li id="btn3">按鈕3</li>
</ul>
var btn1 = document.getElementById('btn1');
var btn2 = document.getElementById('btn2');
var btn3 = document.getElementById('btn3');
// my_event 在前面定義了
my_event.myAddFun(btn1, 'click', function(event){
    alert('btn1點擊');
});
my_event.myAddFun(btn2, 'click', function(event){
    alert('btn2點擊');
});
my_event.myAddFun(btn3, 'click', function(event){
    alert('btn3點擊');
});

下面咱們在DOM樹層級更高的元素上添加一個事件處理函數,來作事件委託,咱們這麼重寫這段代碼:

var btn = document.getElementById('btn');
my_event.myAddFun(btn, 'click', function(event){
    event = my_event.getMyEvent(event);
    var target = my_event.getMyTarget(event);
    switch(target.id){
        case "btn1":
            alert('btn1點擊');
            break;
        case "btn2":
            alert('btn2點擊');
            break;
        case "btn3":
            alert('btn3點擊');
            break;
    }
});

最適合採用事件委託技術的事件包括:click/mousedown/mouseup/keyup/keydown/keypress,雖然mouseovermouseout事件也有冒泡,但由於很差處理它們而且常常須要從新計算元素位置。

能夠看出,事件委託有如下優勢:

  • 減小內存消耗
  • 動態綁定事件

6.事件模擬

JavaScript的事件模擬主要用來在任意時刻觸發特定事件。

6.1 DOM中的事件模擬

document對象上使用createEvent()方法建立一個event對象。
createEvent()接收一個參數,即要建立的事件類型的字符串。
DOM2級中,全部這些字符串都使用英文複數形式,DOM3級中都變成單數,也能夠是下面中的字符串:

  • UIEvents : 通常化的UI事件(鼠標和鍵盤事件都繼承自UI事件)(DOM3級中UIEvent
  • MouseEvents : 通常化的鼠標事件(DOM3級中MouseEvent
  • MutationEvents : 通常化的DOM滾動事件(DOM3級中MutationEvent
  • HTMLEvents : 通常化的HTML事件(DOM3級中HTMLEvent

建立event以後,咱們須要使用dispatchEvent()方法去觸發這個事件,須要傳入一個參數,參數是須要觸發事件的event對象。

全部支持事件的DOM節點都支持這個方法。事件觸發以後,事件就能照樣冒泡並引起響應事件處理程序的執行。

6.1.1 模擬鼠標事件

使用createEvent()方法傳入MouseEvents建立一個鼠標事件,返回的對象有一個initMouseEvent()方法,用於指定與該鼠標事件相關的信息,有15個參數:

  • type : 字符串,表示觸發的事件類型,如click
  • bubble : 布爾值,表示是否冒泡,爲了精確模擬鼠標事件,一般設置爲true
  • cancelable :布爾值,表示是否能夠取消,爲了精確模擬鼠標事件,一般設置爲true
  • view : 與事件關聯的視圖,基本都設置爲document.defaultView
  • detail : 整數,與事件有關的詳細信息,基本設置爲0
  • screenX : 整數,事件相對屏幕的X座標
  • screenY : 整數,事件相對屏幕的Y座標
  • clientX : 整數,事件相對視口的X座標
  • clientY : 整數,事件相對視口的Y座標
  • ctrlKey : 布爾值,表示是否按下Ctrl鍵,默認false
  • altKey : 布爾值,表示是否按下Alt鍵,默認false
  • shiftKey : 布爾值,表示是否按下Shift鍵,默認false
  • metaKey : 布爾值,表示是否按下Meta鍵,默認false
  • button : 整數,表示按下哪一個鼠標鍵,默認0
  • relatedTarget : 對象,表示與事件相關的對象,只在mouseovermouseout時使用

案例:

var btn = document.getElementById('btn');
var myEvent = document.createEvent('MouseEvents');
myEvent.initMouseEvent(
    'click', true, true, document.defaultView, 
    0, 0, 0, 0, 0,
    false, false, false, false, 0, null
)
btn.dispatchEvent(myEvent);

6.1.2 模擬鍵盤事件

DOM3級規定,使用createEvent()方法傳入KeyboardEvent建立一個鍵盤事件,返回的對象有一個initKeyEvent()方法,有8個參數:

  • type : 字符串,表示觸發的事件類型,如keydown
  • bubble : 布爾值,表示是否冒泡,爲了精確模擬鍵盤事件,一般設置爲true
  • cancelable :布爾值,表示是否能夠取消,爲了精確模擬鍵盤事件,一般設置爲true
  • view : 與事件關聯的視圖,基本都設置爲document.defaultView
  • key : 整數,表示按下的鍵的鍵碼
  • localtion : 整數,表示按下哪裏的鍵,默認0表示主鍵盤,1表示左,2表示右,3表示數字鍵盤,4表示移動設備(即虛擬鍵盤),5表示手柄
  • modifiers : 字符串,空格分隔的修改件列表,如"shift"
  • repeat : 整數,在一行中按了多少次這個鍵
    因爲DOM3級不提倡使用keypress事件,所以只能用這個方式來模擬keyup/keydown事件。

模擬按住Shift和A鍵的案例:

var btn = document.getElementById('btn');
var myEvent;

// 以DOM3級方式建立
if(document.implementation.hasFeature('KeyboardEvents', '3.0')){
    myEvent = document.createEvent('KeyboardEvent');
    myEvent.initKeyboardEvent(
        'keydown', true, true, document.defaultView,
        'a', 0, 'Shift', 0
    );
}
btn.dispatchEvent(myEvent);

6.1.3 模擬其餘事件

如模擬變更事件HTML事件

  • 模擬變更事件

經過createEvent()傳入MutationEvents參數建立,返回一個initMutationEvent()方法,這個方法接收參數包括:type/bubbles/cancelable/relatedNode/preValue/newValue/attrName/attrChange,下面模擬一個案例:

var btn = document.getElementById('btn');
var myEvent = document.createEvent('MutationEvents');
myEvent.initMutationEvent(
    'DOMNodeInserted', true, false, someNode, '', '', '', 0
);
btn.dispatchEvent(myEvent);
  • 模擬HTML事件

經過createEvent()傳入HTMLEvents參數建立,返回一個initEvent()方法,下面模擬一個案例:

var btn = document.getElementById('btn');
var myEvent = document.createEvent('HTMLEvents');
myEvent.initEvent('focus', true, false);
btn.dispatchEvent(myEvent);

6.1.4 自定義DOM事件

經過createEvent()傳入CustomEvent參數建立,返回一個initCustomEvent()方法,有4個參數:

  • type : 字符串,表示觸發的事件類型,如keydown
  • bubble : 布爾值,表示是否冒泡,爲了精確模擬鍵盤事件,一般設置爲true
  • cancelable :布爾值,表示是否能夠取消,爲了精確模擬鍵盤事件,一般設置爲true
  • detail : 對象,任意值,保存在event對象的detail屬性中

案例:

var btn = document.getElementById('btn');
var myEvent;

// my_event在前面定義 2.6 跨瀏覽器事件處理程序
my_event.addMyEvent(btn, 'myevent', function(event){
    alert('btn detail ', event.detail);
});

my_event.addMyEvent(document, 'myevent', function(event){
    alert('document detail ', event.detail);
});


// 以DOM3級方式穿件
if(document.implementation.hasFeature('CustomEvents', '3.0')){
    myEvent = document.createEvent('CustomEvent');
    myEvent.initCustomEvent(
        'myevent', true, false, 'hello leo',
    );
    btn.dispatchEvent(myEvent);
}

6.2 IE中的事件模擬

IE8及以前的版本模擬事件和DOM中模擬思路類似:想建立event對象再指定信息,最後觸發。
區別在於,IE中使用document.createEventObject()方法建立event對象,而且不接收參數,返回一個通用event對象,咱們要作的就是給這個event對象添加信息,最後在目標上調用fireEvent()方法,並接受兩個參數(事件處理程序的名稱和event對象)。
在調用fireEvent()方法會自動添加srcElementtype屬性,咱們須要手動添加其餘屬性,下面模擬一個click事件:

var btn = document.getElementById('btn');
var myEvent = document.createEventObject();

myEvent.screenX = 100;
myEvent.screenY = 0;
myEvent.clientX = 100;
myEvent.clientY = 0;
myEvent.ctrlKey = false;
myEvent.altKey = false;
myEvent.shiftKey = false;
myEvent.button = 0;

btn.fireEvent('onclick', event);

參考文章

  1. 《JavaScript高級程序設計》第13章 事件

本部份內容到這結束

Author 王平安
E-mail pingan8787@qq.com
博 客 www.pingan8787.com
微 信 pingan8787
每日文章推薦 https://github.com/pingan8787/Leo_Reading/issues
JS小冊 js.pingan8787.com

bg

相關文章
相關標籤/搜索