javascript中事件的解析(詳細)

來源:互聯網
上載者:User
本篇文章給大家帶來的內容是關於javascript中事件的解析(詳細),有一定的參考價值,有需要的朋友可以參考一下,希望對你有所協助。

JavaScript、瀏覽器、事件之間的關係

JavaScript程式採用了非同步事件驅動編程(Event-driven programming)模型,維基百科對它的解釋是:

事件驅動程式設計(Event-driven programming)是一種電腦程式設計模型。這種模型的程式運行流程是由使用者的動作(如滑鼠的按鍵,鍵盤的按鍵動作)或者是由其他程式的訊息來決定的。相對於批次程式設計(batch programming)而言,程式啟動並執行流程是由程式員來決定。批量的程式設計在初級程式設計教學課程上是一種方式。然而,事件驅動程式設計這種設計模型是在互動程式(Interactive program)的情況下孕育而生的

簡言之,在web前端編程裡面JavaScript通過瀏覽器提供的事件模型API和使用者互動,接收使用者的輸入。

由於使用者的行為是不確定的。這種情境是傳統的同步編程模型沒法解決的,因為你不可能等使用者操作完了才執行後面的代碼。所以在javascript中使用了非同步事件,也就是說:js中的事件都是非同步執行的

事件驅動程式模型基本的實現原理基本上都是使用 事件迴圈(Event Loop),這部分內容涉及瀏覽器事件模型、回調原理。

JavaScript DOM、BOM模型中,同樣非同步還有setTimeout,XMLHTTPRequest這類API並不是JavaScript語言本身就有的。

事件綁定的方法

事件綁定有3種方法:

行內綁定

直接在DOM元素上通過設定on + eventType綁定事件處理常式。例如:

<a href="#none" onclick="alert('clicked')">點擊我</a>

這種方法有兩個缺點:

  1. 事件處理常式和HTML結構混雜在一起,不符合MVX的規範。為了讓內容、表現和行為分開,我們應該避免這種寫法。

  2. 這樣寫的代碼判斷具有全域範圍,可能會產生命名衝突,導致不可預見的嚴重的後果。

在DOM元素上直接重寫事件回呼函數

使用DOM Element上面的on + eventType屬性 API

var el = getElementById('button');  //button是一個<button>元素el.onclick = function(){ alert('button clicked.') };el.onclick = function(){ alert('Button Clicked.') };//實際之彈出'Button Clicked.',函數發生了覆蓋

這種方法也有一個缺點:後綁定的函數會覆蓋之前的函數。比如我們註冊一個window.onload事件,可能會覆蓋某個庫中已有的事件函數。當然,這個可以有解決方案:

function addEvent(element, EventName, fun) {   //EventName = 'on' + eventType    var oldFun = element[EventName];    if (typeof oldFun !== 'function') {        element[EventName] = fun;    } else {        element[EventName] = function() {            oldFun();            fun();        };    }}addEvent(window, "onload", function() { alert('onload 1') });addEvent(window, "onload", function() { alert('onload 2') });

當然,一般情況下使用DOM Ready就可以了,因為JavaScript在DOM載入完就可以執行了

標準Binder 方法

標準的Binder 方法有兩種,addEventListener和attachEvent前者是標準瀏覽器支援的API,後者是IE8以下瀏覽器支援的API:

//例如給一個button註冊click事件var el = getElementById('button');  //button是一個<button>元素if(el.addEventLister){    el.addEventListener("click", function(e){        alert("button clicked.");    },false);}if(el.attachEvent){    el.attachEvent("onclick", function(e){        alert("button clicked.");    });}

需要注意的是:

  1. addEventLister的第一個參數事件類型是不加on首碼的,而attachEvent中需要加on首碼。

  2. addEventLister中的事件回呼函數中的this指向事件元素target本身,而attachEvent中的事件回呼函數的this指向的是window。

  3. addEventLister有第三個參數,true表示事件工作在捕獲階段,false為冒泡階段(預設值:false)。而attachEvent只能工作在冒泡階段。

在chrome中運行如下代碼:

<a href="javascript:alert(1)" onclick="alert(2)" id="link">click me</a><script>    var link = document.getElementById('link');    link.onclick = function() { alert(3); };  //覆蓋了行內的onclick定義    link.addEventListener('click', function() { alert(4); },false);    link.addEventListener('click', function() { alert(5); },false);</script>

點擊後彈出順序是: 3 -> 4 -> 5 -> 1

這裡第4行代碼覆蓋了行內的onclick定義,如果注釋了這一行,輸入順序為: 2 -> 4 -> 5 -> 1,而addEventListener之間不會發生覆蓋。

解除事件綁定

對於上述的前二個方法,解除事件綁定只需要將對應的事件函數設為null,就可以了:

var el = document.getElementById('button');el.onclick = null;

對於上述第三種方法使用removeListen()方法即可,在IE8中,對應使用detachEvent()。注意,他們和上面的註冊方法一一對應,不能混用。

//這是一段錯誤碼,不能實現事件移除//建立一個事件var el = document.getElementById('button');  //button是一個<button>元素if(el.addEventLister){    el.addEventListener("click", function(e){        alert("button clicked.");    },false);}if(el.attachEvent){    el.attachEvent("onclick", function(e){        alert("button clicked.");    });}//試圖移除這個事件if(el.removeEventLister){    el.addEventListener("click", function(e){        alert("button clicked.");    },false);}if(el.detachEvent){    el.datachEvent("onclick", function(e){        alert("button clicked.");    });}//移除失敗

以上的錯誤在於事件函數這樣定義時,雖然看著完全一樣,但在記憶體中地址不一樣。這樣一來,電腦不會認為解除的和綁定的是同一個函數,自然也就不會正確解除。應該這樣寫:

//建立一個事件var el = document.getElementById('button');  //button是一個<button>元素var handler = function(e){alert("button clicked.");};if(el.addEventLister){    el.addEventListener("click", handler,false);}if(el.attachEvent){    el.attachEvent("onclick", handler);}//試圖移除這個事件if(el.removeEventLister){    el.addEventListener("click", handler, false);}if(el.detachEvent){    el.datachEvent("onclick", handler);}//移除成功

事件的捕獲與冒泡

之前說addEventListener函數的第三個參數表示捕獲和冒泡,這個是一個重點!

我自己描述一下他們的定義就是:

冒泡:在一個元素上觸發的某一事件,會在這個元素的父輩元素上會依次由內向外觸發該事件,直到window元素。

捕獲:在一個元素上觸發的某一事件,這個元素的每一層的所有子項目上觸發該事件,並逐層向內,直到所有元素不再有子項目。

如(注:圖片來自百度搜尋)

事件間回到函數參數是一個事件對象,它裡麵包括許多事件屬性和方法,比如,我們可以用以下方式阻止冒泡和預設事件:

//該例子唯寫了handler函數function handler(event) {    event = event || window.event;    //阻止冒泡    if (event.stopPropagation) {        event.stopPropagation();      //標準方法    } else {        event.cancelBubble = true;    // IE8    }    //組織預設事件    if (event.perventDefault) {        event.perventDefault();      //標準方法    } else {        event.returnValue = false;    // IE8    }}

其次,普通註冊事件只能阻止預設事件,不能阻止冒泡

element = document.getElemenById("submit");element.onclick = function(e){    /*...*/    return false;    //通過返回false,阻止冒泡}

事件對象

事件函數中有一個參數是事件對象,它包含了事件發生的所有資訊,比如鍵盤時間會包括點擊了什麼按鍵,包括什麼按鍵組合等等,而滑鼠事件會包括一系列螢幕中的各種座標和點擊類型,甚至拖拽等等。當然,它裡面也會包括很多DOM資訊,比如點擊了什麼元素,拖拽進入了什麼元素,事件的目前狀態等等。

這裡關於事件相容性有必要強調一下:

document.addEventListener('click', function(event) {    event = event || window.event;   //該對象是註冊在window上的    console.log(event);   //可以輸出事件對象看一看, 屬性很多很多    var target = event.target || event.srcElement;  //前者是標準事件目標,後者是IE的事件目標},false);

關於滑鼠事件座標的問題,可以看另一篇部落格:元素和滑鼠事件的距離屬性

事件觸發

除了使用者操作以外,我們也可以寫代碼主動觸發一個事件,以ele元素的click事件為例:

ele.click();   //觸發ele元素上的單擊事件

事件代理

有時候我們需要給不存在的的一段DOM元素繫結事件,比如使用者動態添加的元素,或者一段 Ajax 請求完成後渲染的DOM節點。一般綁定事件的邏輯會在渲染前執行,但綁定的時候找不到元素所以並不能成功。

為瞭解決這個問題,我們通常使用事件代理/委託(Event Delegation)。而且通常來說使用 事件代理的效能會比單獨綁定事件高很多,我們來看個例子。

  • 傳統註冊事件方法,當內容很多時效率低,不支援動態添加元素

<ul id="list">    <li>item-1</li>    <li>item-2</li>    <li>item-3</li>    <li>item-4</li>    <li>item-5</li></ul><script>    var lists = document.getElementsByTagName('li');    for(var i = 0; i < lists.length; ++i){        lists[i].onclick = (function(i){                return function(){                  console.log("item-" + (i + 1));                };            })(i);    }    //添加節點    var list = document.getElementById('list');    var newNode = document.createElement('li');    newNode.innerHTML = "item-6";    list.appendChild(newNode);</script>
  • 事件委託註冊方法,不論內容有多少都只註冊1次,支援動態添加元素:

<ul id="list">    <li>item-1</li>    <li>item-2</li>    <li>item-3</li>    <li>item-4</li>    <li>item-5</li></ul><script>    var list = document.getElementById('list');    var handler = function(e){      e = e || window.event;      var target = e.target || e.srcElement;      if(target.nodeName && target.nodeName === "LI"){        console.log(target.innerHTML);      }    };    if(list.addEventListener){      list.addEventListener("click", handler);    } else {      list.attachEvent("onclick", handler);    }    //添加節點    var list = document.getElementById('list');    var newNode = document.createElement('li');    newNode.innerHTML = "item-6";    list.appendChild(newNode);</script>

事件封裝

很明顯,處理瀏覽器安全色太麻煩了,所以這裡把js中的事件註冊相關函數封裝一下,作為整理。

//均採用冒泡事件模型var myEventUtil={    //添加事件函數    addEvent: function(ele, event, func){        var target = event.target || event.srcElement;        if(ele.addEventListener){            ele.addEventListener(event, func, false);        } else if(ele.attachEvent) {            ele.attachEvent('on' + event, func);   //func中this是window        } else {            ele['on' + event] = func;    //會發生覆蓋        }    },    //刪除事件函數    delEvent:function(ele, event, func) {        if(ele.removeEventListener){            ele.removeEventListener(event, func, false);        } else if(ele.detachEvent) {            ele.detachEvent('on' + event, func);        } else {            ele['on' + event] = null;        }    },    //擷取觸發事件的源DOM元素    getSrcElement: function(event){        return event.target || event.srcElement;    },    //擷取事件類型    getType: function(event){        return event.type;    },    //擷取事件    getEvent:function(event){        return event || window.event;    },    //阻止事件冒泡    stopPropagation: function(event) {        if(event.stopPropagation) {            event.stopPropagation();        } else {            event.cancelBuble = false;        }    },    //禁用預設行為    preventDefault: function(event){        if(event.preventDefault){            event.preventDefault();        } else {            event.returnValue = false;        }    }};

jQuery中的事件

需要注意的是: JQuery中的事件都工作在冒泡階段,且只能工作在冒泡階段

註冊、解除事件

  • 方法一:

//不會發生覆蓋,但不利於解除,不能動態操作事件<button id="button">here</button>$("#button").click(function(){   //註冊一個click事件,當然可以用其他事件名的函數註冊其他事件  console.log("clicked");});
  • 方法二:

//不會發生覆蓋,利於解除,不能動態操作事件<button id="button">here</button>//註冊一個事件$("#button").bind("click", function() {    //註冊一個click事件,當然可以用其他事件名的函數註冊其他事件  console.log("clicked");});//當然還可以這樣寫,給事件指定命名空間$(document).bind('click.handler1', function() { console.log(1);})$(document).bind('click.handler2', function() { console.log(2);})//解除一個事件$("#button").unbind(".handler1");    //解除元素上所以handler1命名空間中的事件$("#button").unbind('click.handler2');   // 解除元素上的click.handler2事件$("#button").unbind('click');            // 解除元素上所有點擊事件$("#button").unbind()                    // 解除元素上所有事件//bind()方法還介受3個參數形式,這裡就不贅述了,感興趣可以自己看看相關資料。
  • 方法三:

//不會發生覆蓋,但不利於解除,能動態操作事件,依賴於事件冒泡//註冊事件$(document).delegate(".item", "click", function(){console.log(this.innerHTML);});   //第一個是選取器, 第二個是事件類型, 第三個是事件函數//移除事件$(document).undelegate(".item", "click", handler);  //移除元素上指定事件$(document).undelegate(".item", "click");  //移除元素上所有click事件$(document).undelegate(".item");  //移除元素上所有事件
  • 方法四:

//不會發生覆蓋,但不利於解除,能動態操作事件,不依賴於事件冒泡//註冊事件#(".item").live("click", function(){console.log(this.innerHTML);})  //第一參數是事件類型, 第二參數是事件函數//移除事件$(".item").die("click", handler);  //移除元素上指定click事件$(".item").die("click");  //移除元素上所有click事件
  • 兩個簡化方法:

//hover方法$("#button").hover(function(){        //滑鼠移入時的動作,不冒泡    }, function(){        //滑鼠移出時的動作,不冒泡});//toggle方法$("#button").toggle(function(){        //第一次點擊時的動作    }, function(){        //第二次點擊時的動作}, .../*可以放多個函數,依次迴圈響應*/);

事件觸發

//不能觸發addEventListener和attachEvent//主動觸發一個事件$("#button").trigger("click");   //觸發所有click事件$("#button").trigger("click.handler1");   //觸發所有click.handler1事件$("#button").trigger(".handler1");   //觸發所有handler1命名空間的事件$("#button").trigger("click!");   //觸發所有沒有命名空間的click事件$("#button").trigger(event);   //在該元素上觸發和事件event一樣的事件$("#button").trigger({type:"click", sync: true});   //觸發click事件,同步

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.