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