首頁  >  文章  >  web前端  >  javascript中事件的解析(詳細)

javascript中事件的解析(詳細)

不言
不言原創
2018-09-10 16:28:482056瀏覽

這篇文章帶給大家的內容是關於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>点击我</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.',函数发生了覆盖</button>

這個方法也有一個缺點:後綁定的函數會覆蓋先前的函數。例如我們註冊一個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加載完就可以執行了

標準綁定方法

標準的綁定方法有兩種,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.");
    });
}</button>

需要注意的是:

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

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

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

在chrome中執行以下程式碼:

<a>click me</a>
<script>
    var link = document.getElementById(&#39;link&#39;);
    link.onclick = function() { alert(3); };  //覆盖了行内的onclick定义
    link.addEventListener(&#39;click&#39;, function() { alert(4); },false);
    link.addEventListener(&#39;click&#39;, 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.");
    });
}

//移除失败</button>

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

//建立一个事件
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);
}

//移除成功</button>

事件的捕獲與冒泡

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

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

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

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

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

javascript中事件的解析(詳細)

事件间回到函数参数是一个事件对象,它里面包括许多事件属性和方法,比如,我们可以用以下方式阻止冒泡和默认事件:

//该例子只写了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)。而且通常来说使用 事件代理的性能会比单独绑定事件高很多,我们来看个例子。

  • 传统注册事件方法,当内容很多时效率低,不支持动态添加元素


        
  • item-1
  •     
  • item-2
  •     
  • item-3
  •     
  • item-4
  •     
  • item-5
<script> var lists = document.getElementsByTagName(&#39;li&#39;); 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(&#39;list&#39;); var newNode = document.createElement(&#39;li&#39;); newNode.innerHTML = "item-6"; list.appendChild(newNode); </script>
  • 事件委托注册方法,不论内容有多少都只注册1次,支持动态添加元素:


        
  • item-1
  •     
  • item-2
  •     
  • item-3
  •     
  • item-4
  •     
  • item-5
<script> var list = document.getElementById(&#39;list&#39;); 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(&#39;list&#39;); var newNode = document.createElement(&#39;li&#39;); 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>here</button>
$("#button").click(function(){   //注册一个click事件,当然可以用其他事件名的函数注册其他事件
  console.log("clicked");
});
  • 方法二:

//不会发生覆盖,利于解除,不能动态操作事件
<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事件,同步

相关推荐:

带你快速理解javascript中的事件模型

JavaScript事件类型中UI事件详解_javascript技巧

以上是javascript中事件的解析(詳細)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn