摘要:瀏覽器事件之間的關(guān)系程序采用了異步事件驅(qū)動編程模型,維基百科對它的解釋是事件驅(qū)動程序設(shè)計是一種電腦程序設(shè)計模型。事件驅(qū)動程序模型基本的實(shí)現(xiàn)原理基本上都是使用事件循環(huán),這部分內(nèi)容涉及瀏覽器事件模型回調(diào)原理。
JavaScript、瀏覽器、事件之間的關(guān)系
JavaScript程序采用了異步事件驅(qū)動編程(Event-driven programming)模型,維基百科對它的解釋是:
事件驅(qū)動程序設(shè)計(Event-driven programming)是一種電腦程序設(shè)計模型。這種模型的程序運(yùn)行流程是由用戶的動作(如鼠標(biāo)的按鍵,鍵盤的按鍵動作)或者是由其他程序的消息來決定的。相對于批處理程序設(shè)計(batch programming)而言,程序運(yùn)行的流程是由程序員來決定。批量的程序設(shè)計在初級程序設(shè)計教學(xué)課程上是一種方式。然而,事件驅(qū)動程序設(shè)計這種設(shè)計模型是在交互程序(Interactive program)的情況下孕育而生的
簡言之,在web前端編程里面JavaScript通過瀏覽器提供的事件模型API和用戶交互,接收用戶的輸入。
由于用戶的行為是不確定的。這種場景是傳統(tǒng)的同步編程模型沒法解決的,因?yàn)槟悴豢赡艿扔脩舨僮魍炅瞬艌?zhí)行后面的代碼。所以在javascript中使用了異步事件,也就是說:js中的事件都是異步執(zhí)行的。
事件驅(qū)動程序模型基本的實(shí)現(xiàn)原理基本上都是使用 事件循環(huán)(Event Loop),這部分內(nèi)容涉及瀏覽器事件模型、回調(diào)原理。
JavaScript DOM、BOM模型中,同樣異步的還有setTimeout,XMLHTTPRequest這類API并不是JavaScript語言本身就有的。
事件綁定的方法事件綁定有3種方法:
行內(nèi)綁定直接在DOM元素上通過設(shè)置on + eventType綁定事件處理程序。例如:
點(diǎn)擊我
這種方法有兩個缺點(diǎn):
事件處理程序和HTML結(jié)構(gòu)混雜在一起,不符合MVX的規(guī)范。為了讓內(nèi)容、表現(xiàn)和行為分開,我們應(yīng)該避免這種寫法。
這樣寫的代碼判斷具有全局作用域,可能會產(chǎn)生命名沖突,導(dǎo)致不可預(yù)見的嚴(yán)重的后果。
在DOM元素上直接重寫事件回調(diào)函數(shù)使用DOM Element上面的on + eventType屬性 API
var el = getElementById("button"); //button是一個
這種方法也有一個缺點(diǎn):后綁定的函數(shù)會覆蓋之前的函數(shù)。比如我們注冊一個window.onload事件,可能會覆蓋某個庫中已有的事件函數(shù)。當(dāng)然,這個可以有解決方法:
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") });
當(dāng)然,一般情況下使用DOM Ready就可以了,因?yàn)镴avaScript在DOM加載完就可以執(zhí)行了
標(biāo)準(zhǔn)綁定方法標(biāo)準(zhǔn)的綁定方法有兩種,addEventListener和attachEvent前者是標(biāo)準(zhǔn)瀏覽器支持的API,后者是IE8以下瀏覽器支持的API:
//例如給一個button注冊click事件 var el = getElementById("button"); //button是一個
需要注意的是:
addEventLister的第一個參數(shù)事件類型是不加on前綴的,而attachEvent中需要加on前綴。
addEventLister中的事件回調(diào)函數(shù)中的this指向事件元素target本身,而attachEvent中的事件回調(diào)函數(shù)的this指向的是window。
addEventLister有第三個參數(shù),true表示事件工作在捕獲階段,false為冒泡階段(默認(rèn)值:false)。而attachEvent只能工作在冒泡階段。
在chrome中運(yùn)行如下代碼:
click me
點(diǎn)擊后彈出順序是: 3 -> 4 -> 5 -> 1
這里第4行代碼覆蓋了行內(nèi)的onclick定義,如果注釋了這一行,輸入順序?yàn)椋?2 -> 4 -> 5 -> 1,而addEventListener之間不會發(fā)生覆蓋。
解除事件綁定對于上述的前二個方法,解除事件綁定只需要將對應(yīng)的事件函數(shù)設(shè)為null,就可以了:
var el = document.getElementById("button"); el.onclick = null;
對于上述第三種方法使用removeListen()方法即可,在IE8中,對應(yīng)使用detachEvent()。注意,他們和上面的注冊方法一一對應(yīng),不能混用。
//這是一段錯誤代碼,不能實(shí)現(xiàn)事件移除 //建立一個事件 var el = document.getElementById("button"); //button是一個
以上的錯誤在于事件函數(shù)這樣定義時,雖然看著完全一樣,但在內(nèi)存中地址不一樣。這樣一來,電腦不會認(rèn)為解除的和綁定的是同一個函數(shù),自然也就不會正確解除。應(yīng)該這樣寫:
//建立一個事件 var el = document.getElementById("button"); //button是一個事件的捕獲與冒泡
之前說addEventListener函數(shù)的第三個參數(shù)表示捕獲和冒泡,這個是一個重點(diǎn)!
我自己描述一下他們的定義就是:
冒泡:在一個元素上觸發(fā)的某一事件,會在這個元素的父輩元素上會依次由內(nèi)向外觸發(fā)該事件,直到window元素。捕獲:在一個元素上觸發(fā)的某一事件,這個元素的每一層的所有子元素上觸發(fā)該事件,并逐層向內(nèi),直到所有元素不再有子元素。
如下圖(注:圖片來自百度搜索)
事件間回到函數(shù)參數(shù)是一個事件對象,它里面包括許多事件屬性和方法,比如,我們可以用以下方式阻止冒泡和默認(rèn)事件:
//該例子只寫了handler函數(shù) function handler(event) { event = event || window.event; //阻止冒泡 if (event.stopPropagation) { event.stopPropagation(); //標(biāo)準(zhǔn)方法 } else { event.cancelBubble = true; // IE8 } //組織默認(rèn)事件 if (event.perventDefault) { event.perventDefault(); //標(biāo)準(zhǔn)方法 } else { event.returnValue = false; // IE8 } }
其次,普通注冊事件只能阻止默認(rèn)事件,不能阻止冒泡
element = document.getElemenById("submit"); element.onclick = function(e){ /*...*/ return false; //通過返回false,阻止冒泡 }事件對象
事件函數(shù)中有一個參數(shù)是事件對象,它包含了事件發(fā)生的所有信息,比如鍵盤時間會包括點(diǎn)擊了什么按鍵,包括什么組合鍵等等,而鼠標(biāo)事件會包括一系列屏幕中的各種坐標(biāo)和點(diǎn)擊類型,甚至拖拽等等。當(dāng)然,它里面也會包括很多DOM信息,比如點(diǎn)擊了什么元素,拖拽進(jìn)入了什么元素,事件的當(dāng)前狀態(tài)等等。
這里關(guān)于事件兼容性有必要強(qiáng)調(diào)一下:
document.addEventListener("click", function(event) { event = event || window.event; //該對象是注冊在window上的 console.log(event); //可以輸出事件對象看一看, 屬性很多很多 var target = event.target || event.srcElement; //前者是標(biāo)準(zhǔn)事件目標(biāo),后者是IE的事件目標(biāo) },false);
關(guān)于鼠標(biāo)事件坐標(biāo)的問題,可以看另一篇博客:元素和鼠標(biāo)事件的距離屬性
事件觸發(fā)除了用戶操作以外,我們也可以寫代碼主動觸發(fā)一個事件,以ele元素的click事件為例:
ele.click(); //觸發(fā)ele元素上的單擊事件事件代理
有時候我們需要給不存在的的一段DOM元素綁定事件,比如用戶動態(tài)添加的元素,或者一段 Ajax 請求完成后渲染的DOM節(jié)點(diǎn)。一般綁定事件的邏輯會在渲染前執(zhí)行,但綁定的時候找不到元素所以并不能成功。
為了解決這個問題,我們通常使用事件代理/委托(Event Delegation)。而且通常來說使用 事件代理的性能會比多帶帶綁定事件高很多,我們來看個例子。
傳統(tǒng)注冊事件方法,當(dāng)內(nèi)容很多時效率低,不支持動態(tài)添加元素
事件委托注冊方法,不論內(nèi)容有多少都只注冊1次,支持動態(tài)添加元素:
很明顯,處理瀏覽器兼容太麻煩了,所以這里把js中的事件注冊相關(guān)函數(shù)封裝一下,作為整理。
//均采用冒泡事件模型 var myEventUtil={ //添加事件函數(shù) 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; //會發(fā)生覆蓋 } }, //刪除事件函數(shù) 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; } }, //獲取觸發(fā)事件的源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; } }, //禁用默認(rèn)行為 preventDefault: function(event){ if(event.preventDefault){ event.preventDefault(); } else { event.returnValue = false; } } };jQuery中的事件
需要注意的是: JQuery中的事件都工作在冒泡階段,且只能工作在冒泡階段
注冊、解除事件方法一:
//不會發(fā)生覆蓋,但不利于解除,不能動態(tài)操作事件 $("#button").click(function(){ //注冊一個click事件,當(dāng)然可以用其他事件名的函數(shù)注冊其他事件 console.log("clicked"); });
方法二:
//不會發(fā)生覆蓋,利于解除,不能動態(tài)操作事件 //注冊一個事件 $("#button").bind("click", function() { //注冊一個click事件,當(dāng)然可以用其他事件名的函數(shù)注冊其他事件 console.log("clicked"); }); //當(dāng)然還可以這樣寫,給事件指定命名空間 $(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"); // 解除元素上所有點(diǎn)擊事件 $("#button").unbind() // 解除元素上所有事件 //bind()方法還介受3個參數(shù)形式,這里就不贅述了,感興趣可以自己看看相關(guān)資料。
方法三:
//不會發(fā)生覆蓋,但不利于解除,能動態(tài)操作事件,依賴于事件冒泡 //注冊事件 $(document).delegate(".item", "click", function(){console.log(this.innerHTML);}); //第一個是選擇器, 第二個是事件類型, 第三個是事件函數(shù) //移除事件 $(document).undelegate(".item", "click", handler); //移除元素上指定事件 $(document).undelegate(".item", "click"); //移除元素上所有click事件 $(document).undelegate(".item"); //移除元素上所有事件
方法四:
//不會發(fā)生覆蓋,但不利于解除,能動態(tài)操作事件,不依賴于事件冒泡 //注冊事件 #(".item").live("click", function(){console.log(this.innerHTML);}) //第一參數(shù)是事件類型, 第二參數(shù)是事件函數(shù) //移除事件 $(".item").die("click", handler); //移除元素上指定click事件 $(".item").die("click"); //移除元素上所有click事件
兩個簡化方法:
//hover方法 $("#button").hover(function(){ //鼠標(biāo)移入時的動作,不冒泡 }, function(){ //鼠標(biāo)移出時的動作,不冒泡 }); //toggle方法 $("#button").toggle(function(){ //第一次點(diǎn)擊時的動作 }, function(){ //第二次點(diǎn)擊時的動作 }, .../*可以放多個函數(shù),依次循環(huán)響應(yīng)*/);事件觸發(fā)
//不能觸發(fā)addEventListener和attachEvent //主動觸發(fā)一個事件 $("#button").trigger("click"); //觸發(fā)所有click事件 $("#button").trigger("click.handler1"); //觸發(fā)所有click.handler1事件 $("#button").trigger(".handler1"); //觸發(fā)所有handler1命名空間的事件 $("#button").trigger("click!"); //觸發(fā)所有沒有命名空間的click事件 $("#button").trigger(event); //在該元素上觸發(fā)和事件event一樣的事件 $("#button").trigger({type:"click", sync: true}); //觸發(fā)click事件,同步
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/97589.html
摘要:前端渲染過程的二三事本文不會介紹整個前端渲染過程的步驟,只是記錄最近閱讀的文章的些許思考和感悟。那么現(xiàn)在我們可以明白這個問題的關(guān)鍵所在了,因?yàn)樵诖蟛糠猪撁嬷惺菗碛械模捎谄浣馕鲰樞?,那么在事件之前必定已?jīng)成功構(gòu)造樹。 前端渲染過程的二三事 本文不會介紹整個前端渲染過程的步驟,只是記錄最近閱讀的文章的些許思考和感悟。(文章地址一(系列),文章地址二) 希望大家在閱讀這篇文章之前能將上述...
摘要:瀏覽器中的瀏覽器中的通常稱為客戶端的客戶端對象是所有客戶端特性和的主要接入點(diǎn)。瀏覽器不會執(zhí)行之間的代碼中的事件處理程序當(dāng)腳本所在的文件被載入的時候??梢赃_(dá)到延遲腳本的執(zhí)行,直到文檔載入和解析完成,才方可操作。 web瀏覽器中的JavaScriptweb瀏覽器中的js通常稱為客戶端的JavaScript 客戶端 JavaScript window對象是所有客戶端JavaScript特性和...
摘要:如果沒有前一個網(wǎng)頁,則等于屬性。該事件在網(wǎng)頁查詢本地緩存之前發(fā)生。如果使用持久連接,則返回值等同于屬性的值。返回當(dāng)前網(wǎng)頁結(jié)構(gòu)生成時即屬性變?yōu)?,以及相?yīng)的事件發(fā)生時的毫秒時間戳。 window.performance.timing下的屬性 navigationStart 當(dāng)前瀏覽器窗口的前一個網(wǎng)頁關(guān)閉,發(fā)生unload事件時的Unix毫秒時間戳。如果沒有前一個網(wǎng)頁,則等于fetchSta...
摘要:如果沒有前一個網(wǎng)頁,則等于屬性。該事件在網(wǎng)頁查詢本地緩存之前發(fā)生。如果使用持久連接,則返回值等同于屬性的值。返回當(dāng)前網(wǎng)頁結(jié)構(gòu)生成時即屬性變?yōu)?,以及相?yīng)的事件發(fā)生時的毫秒時間戳。 window.performance.timing下的屬性 navigationStart 當(dāng)前瀏覽器窗口的前一個網(wǎng)頁關(guān)閉,發(fā)生unload事件時的Unix毫秒時間戳。如果沒有前一個網(wǎng)頁,則等于fetchSta...
閱讀 2481·2021-11-19 09:59
閱讀 2006·2019-08-30 15:55
閱讀 938·2019-08-29 13:30
閱讀 1342·2019-08-26 10:18
閱讀 3091·2019-08-23 18:36
閱讀 2394·2019-08-23 18:25
閱讀 1168·2019-08-23 18:07
閱讀 441·2019-08-23 17:15