Javascript-事件

一、事件流

定義:事件流描述的是從頁面中接收事件的順序。
但有意思的是,IE 和Netscape 開發(fā)團隊居然提出了差不多是完全相反的事件流的概念。IE 的事件流是事件冒泡流,而Netscape Communicator 的事件流是事件捕獲流。

1、事件冒泡(event-bubbling)

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

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
    </head>
    <body>
        <div id="myDiv">Click Me</div>
    </body>
</html>

如上代碼,如果點擊了id位myDIv的元素后,之后會按照如圖傳播


event-bobbling.PNG

2、事件捕獲(event-capturing)

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

event-capturing.PNG

由于老版本的瀏覽器不支持,因此很少有人使用事件捕獲。我們也建議讀者放心地使用事件冒泡,在有特殊需要時再使用事件捕獲。
3、DOM事件流
“DOM2級事件”規(guī)定的事件流包括三個階段:事件捕獲階段、處于目標階段和事件冒泡階段。
DOM事件流.PNG

在DOM 事件流中,實際的目標(<div>元素)在捕獲階段不會接收到事件。這意味著在捕獲階段,事件從document 到<html>再到<body>后就停止了。下一個階段是“處于目標”階段,于是事件在<div>上發(fā)生,并在事件處理(后面將會討論這個概念)中被看成冒泡階段的一部分。然后,冒泡階段發(fā)生,事件又傳播回文檔。
多數(shù)支持DOM 事件流的瀏覽器都實現(xiàn)了一種特定的行為;即使“DOM2 級事件”規(guī)范明確要求捕獲階段不會涉及事件目標,但IE9、Safari、Chrome、Firefox 和Opera 9.5 及更高版本都會在捕獲階段觸發(fā)事件對象上的事件。結(jié)果,就是有兩個機會在目標對象上面操作事件。

二、事件處理程序

我們先來了解幾個概念

事件:事件就是用戶或瀏覽器自身執(zhí)行的某種動作。諸如click、load 和mouseover,都是事件的名字

事件處理程序:響應(yīng)某個事件的函數(shù)就叫做事件處理程序(或事件偵聽器)。事件處理程序的名字以"on"開頭,因此click 事件的事件處理程序就是onclick,load 事件的事件處理程序就是onload。為事件指定處理程序的方式有好幾種

1、HTML事件處理程序

某個元素支持的每種事件,都可以使用一個與相應(yīng)事件處理程序同名的HTML 特性來指定。這個特性的值應(yīng)該是能夠執(zhí)行的JavaScript 代碼

<input type="button" value="Click me" onclick="alert('hello')">

特點:

  • 會創(chuàng)建一個封裝著元素屬性值的函數(shù)。這個函數(shù)中有一個局部變量event,也就是事件對象。通過event 變量,可以直接訪問事件對象,你不用自己定義它,也不用從函數(shù)的參數(shù)列表中讀取。
<button onclick="alert(event.type)">輸出click</button>
  • 在這個函數(shù)內(nèi)部,this 值等于事件的目標元素

2、DOM0 級事件處理程序

簡單的說:就是將一個函數(shù)賦值給一個事件處理程序?qū)傩浴@纾?/p>

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

使用DOM0 級方法指定的事件處理程序被認為是元素的方法。因此,這時候的事件處理程序是在元素的作用域中運行;換句話說,程序中的this 引用當前元素。

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

以這種方式添加的事件處理程序會在事件流的冒泡階段被處理。
也可以刪除通過DOM0 級方法指定的事件處理程序,只要像下面這樣將事件處理程序?qū)傩缘闹翟O(shè)置為null 即可

btn.onclick = null;

DOM0 級對每個事件只支持一個事件處理程序。

2、DOM2級事件處理程序

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

btn.addEventListener("click",function(){
    alert(this.id);
},false);

通過addEventListener()添加的事件處理程序只能使用removeEventListener()來移除;移除時傳入的參數(shù)與添加處理程序時使用的參數(shù)相同。這也意味著通過addEventListener()添加的匿名函數(shù)將無法移除

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

IE9、Firefox、Safari、Chrome 和Opera 支持DOM2 級事件處理程序。

3、IE事件處理程序

attchEvent(eventType, function) detachEvent(eventType, function)

在使用DOM0 級方法的情況下,事件處理程序會在其所屬元素的作用域內(nèi)運行;在使用attachEvent()方法的情況下,事件處理程序會在全局作用域中運行,因此this 等于window。
btn.attachEvent("oncick", function(){
    alert(this === window);
});

三、事件對象

在觸發(fā)DOM上的某個事件時,會產(chǎn)生一個事件對象event,這個對象中包含著所有與事件有關(guān)的信息。包括導(dǎo)致事件的元素、事件的類型以及其他與特定事件相關(guān)的信息。

1、DOM中的事件對象

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

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

preventDefault()阻止特定事件的默認行為

var link = document.getElementById("myLink");
link.onclick = function(event){
    event.preventDefault();
}

stopPropagation()立即停止事件在DOM層次的傳播,即取消進一步的事件捕獲或冒泡

btn.onclick = function(event) {
    alert("clicked");
    event.stopPropagation();
};
document.body.onclick = function(event) {
    alert("body clicked");
}

eventPhase屬性確定事件當前正位于事件流的哪個階段
捕獲階段:1
目標對象上:2
冒泡階段:3

btn.onclick = function(event) {
    alert(event.eventPhase);  //2
};
document.body.addEventListener("click",
    function(event){
        alert(event.eventPhase); //1
    },true);
document.body.onclick = function(event){
    alert(event.eventPhase);  //3
}

**注意:只有在事件處理程序執(zhí)行期間,event 對象才會存在;一旦事件處理程序執(zhí)行完成,event 對象就會被銷毀。 **

2、IE中的事件對象

  • 在使用DOM0 級方法添加事件處理程序時,event 對象作為window 對象的一個屬性存在

  • attachEvent()添加的,那么就會有一個event 對象作為參數(shù)被傳入事件處理程序函數(shù)中

3、兼容性的事件對象

var EventUtil = {
    addHandler:function(element, type, handler){
        if(element.addEventListener){
            element.addEventListener(type, handler, false);
        }else if(element.attchEvent) {
            element.attachEvent("on" + type, handler);
        }else{
            element["on" + type] = handler;
        }
    },
    removeHandler: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;
        }
    },
    getEvent: function(event){
        return event ? event : window.event;
    },
    getTarget:function(event){
        return event.target || event.srcElement;
    },
    preventDefault:function(event) {
        if(event.preventDefault) {
            event.preventDefault();
        }else{
            event.returnValue = false;
        }
    },
    stopPropagation: function(event) {
        if(event.stopPropagation) {
            event.stopPropagation();
        }else{
            event.cancelBubble = true;
        }
    }
};

四、事件類型

1、UI事件

load
當頁面完全加載后(包括所有圖像、JavaScript 文件、CSS 文件等外部資源),就會觸發(fā)window 上面的load 事件。
unload
與load 事件對應(yīng)的是unload 事件,這個事件在文檔被完全卸載后觸發(fā)。只要用戶從一個頁面切換到另一個頁面,就會發(fā)生unload 事件。而利用這個事件最多的情況是清除引用,以避免內(nèi)存泄漏。
resize
當瀏覽器窗口被調(diào)整到一個新的高度或?qū)挾葧r,就會觸發(fā)resize 事件。

EventUtil.addHandler(window, "resize", function(event){
    alert("Resized");
});

scroll

2、焦點事件

focus在元素獲得焦點時觸發(fā)
blur在元素失去焦點時觸發(fā)

3、鼠標和滾輪事件

click
dblclick
mousedown
mouseup
mouseenter
mouseleave
mouseover
mouseout

  • 客戶區(qū)坐標位置
var div = document.getElementById('myDiv');
EventUtil.addHandler(div, "click", function(event){
    event = EventUtil.getEvent(event);
    alert("Client coordinates:" + event.clientX + "," + event.clientY);
});
  • 頁面坐標位置
    頁面坐標通過事件對象的pageX 和pageY 屬性,能告訴你事件是在頁面中的什么位置發(fā)生的
  • 屏幕坐標位置
    過screenX 和screenY 屬性就可以確定鼠標事件發(fā)生時鼠標指針相對于整個屏幕的坐標信息

…………更多內(nèi)容參考紅皮書

五、內(nèi)存和性能

1、事件委托

假設(shè)如果我們?yōu)轫撁嫔系拿恳粋€需要事件的元素都添加事件處理程序,那么會嚴重影響頁面的整體性能。原因有一下幾點:

  • 每個函數(shù)都是對象,都會占用內(nèi)存
  • 必須事先指定所有事件處理程序而導(dǎo)致的DOM訪問次數(shù),會延遲整個頁面的交互就緒時間
    所以我們使用事件委托的方式,其原理就是使用事件的冒泡機制。具體看例子
    平常的操作:
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
    </head>
    <body>
    <ul id="myLinks">
        <li id="goSomewhere">Go somewhere</li>
        <li id="doSomething">Do something</li>
        <li id="sayHi">Say hi</li>
    </ul>
    <script src="eventUtil.js"></script>
    <script>
    var item1 = document.getElementById("goSomewhere");
    var item2 = document.getElementById("doSomething");
    var item3 = document.getElementById("sayHi");
    EventUtil.addHandler(item1, "click", function(event){
        location.;
    });
    EventUtil.addHandler(item2, "click", function(event){
        document.title = "I changed the document's title";
    });
    EventUtil.addHandler(item3, "click", function(event){
        alert("hi");
    })
    </script>
    </body>
</html>

利用事件委托:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
    </head>
    <body>
    <ul id="myLinks">
        <li id="goSomewhere">Go somewhere</li>
        <li id="doSomething">Do something</li>
        <li id="sayHi">Say hi</li>
    </ul>
    <script src="eventUtil.js"></script>
    <script>
    var list = document.getElementById("myLinks");
    EventUtil.addHandler(list, "click", function(event){
        event = EventUtil.getEvent(event);
        var target = EventUtil.getTarget(event);
        switch(target.id){
            case "doSomething":
                document.title = "i changed the title";
                break;
            case "goSomewhere":
                location.;
                break;
            case "sayHi":
                alert("hi");
                break;
        }
    })
    </script>
    </body>
</html>

這樣我們只需要把事件處理程序放到ul這一個DOM節(jié)點上,然后根據(jù)target的值來判斷用哪個事件,就可以避免頁面中寫大量的事件處理程序(event handler)了,也減少了DOM的操作。

2、移除event handler

事件委托是限制event handler的數(shù)量。另外可以通過移除不用的空事件處理程序(dangling event handler)來提高性能。
造成dangling event handler的原因可能有兩種:

  • 從文檔中移除帶有事件處理程序的元素時,這可能是通過純粹的DOM操作,例如使用removeChild()和replaceChild()方法
  • 使用innerHTML 替換頁面中某一部分的時候。如果帶有事件處理程序的元素被innerHTML 刪除了,那么原來添加到元素中的事件處理程序極有可能無法被當作垃圾回收
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
    </head>
    <body>
        <div id="myDiv">
            <input type="button" value="Click Me" id="myBtn">
        </div>
        <script>
        var btn = document.getElementById("myBtn");
        btn.onclick = function(){
            document.getElementById("myDiv").innerHTML = "Processing……"
        }
        </script>
    </body>
</html>

當按鈕被從頁面中移除時,它還帶著一個事件處理程,仍然占用著內(nèi)存。
解決辦法:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
    </head>
    <body>
        <div id="myDiv">
            <input type="button" value="Click Me" id="myBtn">
        </div>
        <script>
        var btn = document.getElementById("myBtn");
        btn.onclick = function(){
            btn.onclick = null;  //移除事件處理程序
            document.getElementById("myDiv").innerHTML = "Processing……"
        }
        </script>
    </body>
</html>

建議瀏覽器卸載頁面之前移除頁面中的所有事件處理程序。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,527評論 6 544
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,687評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,640評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,957評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,682評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 56,011評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,009評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,183評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,714評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,435評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,665評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,148評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,838評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,251評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,588評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,379評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,627評論 2 380

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

  • JavaScript 與 HTML 之間的交互是通過事件實現(xiàn)的。事件,就是文檔或瀏覽器窗口中發(fā)生的一些特定的交互瞬...
    threetowns閱讀 349評論 0 0
  • 事件流 事件流描述的是從頁面中接受事件的順序。但是IE和Netscape開發(fā)團隊提出了差不多相反的事件流的概念。I...
    losspm閱讀 268評論 0 0
  • 事件處理程序在應(yīng)用中是必不可少的,雖然現(xiàn)在很多框架都有自己實現(xiàn)事件處理方法,但是熟知原生才能讓我們應(yīng)對各種各樣的需...
    俗三瘋閱讀 300評論 0 1
  • JavaScript與HTML之間的交互是通過事件完成的。可以使用事件偵聽器來預(yù)訂事件,正在傳統(tǒng)軟件工程中被稱為觀...
    yohn閱讀 199評論 1 1
  • 一.事件 事件是用戶或瀏覽器自身執(zhí)行的某種動作,這是我自己的理解。 二.事件流 事件流描述的是從頁面中接收事件的順...
    卓三陽閱讀 2,238評論 1 2