摘要:瀏覽器事件之間的關系程序采用了異步事件驅動編程模型,維基百科對它的解釋是事件驅動程序設計是一種電腦程序設計模型。事件驅動程序模型基本的實現原理基本上都是使用事件循環,這部分內容涉及瀏覽器事件模型回調原理。
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綁定事件處理程序。例如:
點擊我
這種方法有兩個缺點:
事件處理程序和HTML結構混雜在一起,不符合MVX的規范。為了讓內容、表現和行為分開,我們應該避免這種寫法。
這樣寫的代碼判斷具有全局作用域,可能會產生命名沖突,導致不可預見的嚴重的后果。
在DOM元素上直接重寫事件回調函數使用DOM Element上面的on + eventType屬性 API
var el = getElementById("button"); //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是一個
需要注意的是:
addEventLister的第一個參數事件類型是不加on前綴的,而attachEvent中需要加on前綴。
addEventLister中的事件回調函數中的this指向事件元素target本身,而attachEvent中的事件回調函數的this指向的是window。
addEventLister有第三個參數,true表示事件工作在捕獲階段,false為冒泡階段(默認值:false)。而attachEvent只能工作在冒泡階段。
在chrome中運行如下代碼:
click me
點擊后彈出順序是: 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是一個
以上的錯誤在于事件函數這樣定義時,雖然看著完全一樣,但在內存中地址不一樣。這樣一來,電腦不會認為解除的和綁定的是同一個函數,自然也就不會正確解除。應該這樣寫:
//建立一個事件 var el = document.getElementById("button"); //button是一個事件的捕獲與冒泡
之前說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)。而且通常來說使用 事件代理的性能會比多帶帶綁定事件高很多,我們來看個例子。
傳統注冊事件方法,當內容很多時效率低,不支持動態添加元素
事件委托注冊方法,不論內容有多少都只注冊1次,支持動態添加元素:
很明顯,處理瀏覽器兼容太麻煩了,所以這里把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").click(function(){ //注冊一個click事件,當然可以用其他事件名的函數注冊其他事件 console.log("clicked"); });
方法二:
//不會發生覆蓋,利于解除,不能動態操作事件 //注冊一個事件 $("#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事件,同步
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/54605.html
摘要:前端渲染過程的二三事本文不會介紹整個前端渲染過程的步驟,只是記錄最近閱讀的文章的些許思考和感悟。那么現在我們可以明白這個問題的關鍵所在了,因為在大部分頁面中是擁有的,而由于其解析順序,那么在事件之前必定已經成功構造樹。 前端渲染過程的二三事 本文不會介紹整個前端渲染過程的步驟,只是記錄最近閱讀的文章的些許思考和感悟。(文章地址一(系列),文章地址二) 希望大家在閱讀這篇文章之前能將上述...
摘要:瀏覽器中的瀏覽器中的通常稱為客戶端的客戶端對象是所有客戶端特性和的主要接入點。瀏覽器不會執行之間的代碼中的事件處理程序當腳本所在的文件被載入的時候。可以達到延遲腳本的執行,直到文檔載入和解析完成,才方可操作。 web瀏覽器中的JavaScriptweb瀏覽器中的js通常稱為客戶端的JavaScript 客戶端 JavaScript window對象是所有客戶端JavaScript特性和...
摘要:如果沒有前一個網頁,則等于屬性。該事件在網頁查詢本地緩存之前發生。如果使用持久連接,則返回值等同于屬性的值。返回當前網頁結構生成時即屬性變為,以及相應的事件發生時的毫秒時間戳。 window.performance.timing下的屬性 navigationStart 當前瀏覽器窗口的前一個網頁關閉,發生unload事件時的Unix毫秒時間戳。如果沒有前一個網頁,則等于fetchSta...
摘要:如果沒有前一個網頁,則等于屬性。該事件在網頁查詢本地緩存之前發生。如果使用持久連接,則返回值等同于屬性的值。返回當前網頁結構生成時即屬性變為,以及相應的事件發生時的毫秒時間戳。 window.performance.timing下的屬性 navigationStart 當前瀏覽器窗口的前一個網頁關閉,發生unload事件時的Unix毫秒時間戳。如果沒有前一個網頁,則等于fetchSta...
閱讀 2314·2021-11-08 13:13
閱讀 1245·2021-10-09 09:41
閱讀 1683·2021-09-02 15:40
閱讀 3186·2021-08-17 10:13
閱讀 2546·2019-08-29 16:33
閱讀 3122·2019-08-29 13:17
閱讀 3131·2019-08-29 11:00
閱讀 3295·2019-08-26 13:40