事件綁定分爲兩種:一種是傳統事件綁定(內聯模型,腳本模型),一種是現代事件綁定
(DOM2 級模型)。現代事件綁定在傳統綁定上提供了更強大更方便的功能。windows
一.傳統事件綁定的問題
傳統事件綁定有內聯模型和腳本模型,內聯模型咱們不作討論,基本不多去用。先來看
一下腳本模型,腳本模型將一個函數賦值給一個事件處理函數。瀏覽器
- var box = document.getElementById('box'); //獲取元素
- box.onclick = function () { //元素點擊觸發事件
- alert('Lee');
- };
問題一:一個事件處理函數觸發兩次事件ide
- window.onload = function () { //第一組程序項目或第一個JS 文件
- alert('Lee');
- };
- window.onload = function () { //第二組程序項目或第二個JS 文件
- alert('Mr.Lee');
- };
當兩組程序或兩個JS 文件同時執行的時候,後面一個會把前面一個徹底覆蓋掉。致使
前面的window.onload 徹底失效了。函數
解決覆蓋問題,咱們能夠這樣去解決:this
- window.onload = function () { //第一個要執行的事件,會被覆蓋
- alert('Lee');
- };
- if (typeof window.onload == 'function') { //判斷以前是否有window.onload
- var saved = null; //建立一個保存器
- saved = window.onload; //把以前的window.onload 保存起來
- }
- window.onload = function () { //最終一個要執行事件
- if (saved) saved(); //執行以前一個事件
- alert('Mr.Lee'); //執行本事件的代碼
- };
問題二:事件切換器spa
- box.onclick = toBlue; //第一次執行boBlue()
- function toRed() {
- this.className = 'red';
- this.onclick = toBlue; //第三次執行toBlue(),而後來回切換
- }
- function toBlue() {
- this.className = 'blue';
- this.onclick = toRed; //第二次執行toRed()
- }
這個切換器在擴展的時候,會出現一些問題:
1.若是增長一個執行函數,那麼會被覆蓋對象
- box.onclick = toAlert; //被增長的函數
- box.onclick = toBlue; //toAlert 被覆蓋了
2.若是解決覆蓋問題,就必須包含同時執行,但又出新問題遞歸
- box.onclick = function () { //包含進去,但可讀性下降
- toAlert(); //第一次不會被覆蓋,但第二次又被覆蓋
- toBlue.call(this); //還必須把this 傳遞到切換器裏
- };
綜上的三個問題:覆蓋問題、可讀性問題、this 傳遞問題。咱們來建立一個自定義的事
件處理函數,來解決以上三個問題。seo
- function addEvent(obj, type, fn) { //取代傳統事件處理函數
- var saved = null; //保存每次觸發的事件處理函數
- if (typeof obj['on' + type] == 'function') { //判斷是否是事件
- saved = obj['on' + type]; //若是有,保存起來
- }
- obj['on' + type] = function () { //而後執行
- if (saved) saved(); //執行上一個
- fn.call(this); //執行函數,把this 傳遞過去
- };
- }
- addEvent(window, 'load', function () { //執行到了
- alert('Lee');
- });
- addEvent(window, 'load', function () { //執行到了
- alert('Mr.Lee');
- });
PS:以上編寫的自定義事件處理函數,還有一個問題沒有處理,就是兩個相同函數名
的函數誤註冊了兩次或屢次,那麼應該把多餘的屏蔽掉。那,咱們就須要把事件處理函數進
行遍歷,若是有一樣名稱的函數名就不添加便可。(這裏就不作了)事件
- addEvent(window, 'load', init); //註冊第一次
- addEvent(window, 'load', init); //註冊第二次,應該忽略
- function init() {
- alert('Lee');
- }
用自定義事件函數註冊到切換器上查看效果:
- addEvent(window, 'load', function () {
- var box = document.getElementById('box');
- addEvent(box, 'click', toBlue);
- });
- function toRed() {
- this.className = 'red';
- addEvent(this, 'click', toBlue);
- }
- function toBlue() {
- this.className = 'blue';
- addEvent(this, 'click', toRed);
- }
PS:當你單擊不少不少次切換後,瀏覽器直接卡死,或者彈出一個錯誤:too much
recursion(太多的遞歸)。主要的緣由是,每次切換事件的時候,都保存下來,沒有把無用的
移除,致使越積越多,最後卡死。
- function removeEvent(obj, type) {
- if (obj['on'] + type) obj['on' + type] = null; //刪除事件處理函數
- }
以上的刪除事件處理函數只不過是一刀切的刪除了,這樣雖然解決了卡死和太多遞歸的
問題。但其餘的事件處理函數也一併被刪除了,致使最後得不到本身想要的結果。若是想要
只刪除指定的函數中的事件處理函數,那就須要遍歷,查找。(這裏就不作了)
二.W3C事件處理函數
「DOM2 級事件」定義了兩個方法,用於添加事件和刪除事件處理程序的操做:
addEventListener()和removeEventListener()。全部DOM 節點中都包含這兩個方法,而且它
們都接受3 個參數;事件名、函數、冒泡或捕獲的布爾值(true 表示捕獲,false 表示冒泡)。
- window.addEventListener('load', function () {
- alert('Lee');
- }, false);
- window.addEventListener('load', function () {
- alert('Mr.Lee');
- }, false);
PS:W3C 的現代事件綁定比咱們自定義的好處就是:1.不須要自定義了;2.能夠屏蔽相
同的函數;3.能夠設置冒泡和捕獲。
- window.addEventListener('load', init, false); //第一次執行了
- window.addEventListener('load', init, false); //第二次被屏蔽了
- function init() {
- alert('Lee');
- }
事件切換器
- window.addEventListener('load', function () {
- var box = document.getElementById('box');
- box.addEventListener('click', function () { //不會被誤刪
- alert('Lee');
- }, false);
- box.addEventListener('click', toBlue, false); //引入切換也不會太多遞歸卡死
- }, false);
- function toRed() {
- this.className = 'red';
- this.removeEventListener('click', toRed, false);
- this.addEventListener('click', toBlue, false);
- }
- function toBlue() {
- this.className = 'blue';
- this.removeEventListener('click', toBlue, false);
- this.addEventListener('click', toRed, false);
- }
設置冒泡和捕獲階段
以前咱們上一章瞭解了事件冒泡,即從裏到外觸發。咱們也能夠經過event 對象來阻止
某一階段的冒泡。那麼W3C 現代事件綁定能夠設置冒泡和捕獲。
- document.addEventListener('click', function () {
- alert('document');
- }, true); //把布爾值設置成true,則爲捕獲
- box.addEventListener('click', function () {
- alert('Lee');
- }, true); //把布爾值設置成false,則爲冒泡
三.IE事件處理函數
IE 實現了與DOM 中相似的兩個方法:attachEvent()和detachEvent()。這兩個方法接受
相同的參數:事件名稱和函數。
在使用這兩組函數的時候,先把區別說一下:1.IE 不支持捕獲,只支持冒泡;2.IE 添加
事件不能屏蔽重複的函數;3.IE 中的this 指向的是window 而不是DOM 對象。4.在傳統事
件上,IE 是沒法接受到event 對象的,但使用了attchEvent()卻能夠,但有些區別。
- window.attachEvent('onload', function () {
- var box = document.getElementById('box');
- box.attachEvent('onclick', toBlue);
- });
- function toRed() {
- var that = window.event.srcElement;
- that.className = 'red';
- that.detachEvent('onclick', toRed);
- that.attachEvent('onclick', toBlue);
- }
- function toBlue() {
- var that = window.event.srcElement;
- that.className = 'blue';
- that.detachEvent('onclick', toBlue);
- that.attachEvent('onclick', toRed);
- }
PS:IE 不支持捕獲,無解。IE 不能屏蔽,須要單獨擴展或者自定義事件處理。IE 不能
傳遞this,能夠call 過去。
- window.attachEvent('onload', function () {
- var box = document.getElementById('box');
- box.attachEvent('onclick', function () {
- alert(this === window); //this 指向的window
- });
- });
- window.attachEvent('onload', function () {
- var box = document.getElementById('box');
- box.attachEvent('onclick', function () {
- toBlue.call(box); //把this 直接call 過去
- });
- });
- function toThis() {
- alert(this.tagName);
- }
在傳統綁定上,IE 是沒法像W3C 那樣經過傳參接受event 對象,但若是使用了
attachEvent()卻能夠。
- box.onclick = function (evt) {
- alert(evt); //undefined
- }
- box.attachEvent('onclick', function (evt) {
- alert(evt); //object
- alert(evt.type); //click
- });
- box.attachEvent('onclick', function (evt) {
- alert(evt.srcElement === box); //true
- alert(window.event.srcElement === box); //true
- });
最後,爲了讓IE 和W3C 能夠兼容這個事件切換器,咱們能夠寫成以下方式:
- function addEvent(obj, type, fn) { //添加事件兼容
- if (obj.addEventListener) {
- obj.addEventListener(type, fn);
- } else if (obj.attachEvent) {
- obj.attachEvent('on' + type, fn);
- }
- }
- function removeEvent(obj, type, fn) { //移除事件兼容
- if (obj.removeEventListener) {
- obj.removeEventListener(type, fn);
- } else if (obj.detachEvent) {
- obj.detachEvent('on' + type, fn);
- }
- }
- function getTarget(evt) { //獲得事件目標
- if (evt.target) {
- return evt.target;
- } else if (window.event.srcElement) {
- return window.event.srcElement;
- }
- }
PS:調用忽略,IE 兼容的事件,若是要傳遞this,改爲call 便可。
PS:IE 中的事件綁定函數attachEvent()和detachEvent()可能在實踐中不去使用,有幾個
緣由:1.IE9 就將全面支持W3C 中的事件綁定函數;2.IE 的事件綁定函數沒法傳遞this;3.IE
的事件綁定函數不支持捕獲;4.同一個函數註冊綁定後,沒有屏蔽掉;5.有內存泄漏的問題。
至於怎麼替代,咱們將在之後的項目課程中探討。
四.事件對象的其餘補充
在W3C 提供了一個屬性:relatedTarget;這個屬性能夠在mouseover 和mouseout 事件
中獲取從哪裏移入和從哪裏移出的DOM 對象。
- box.onmouseover = function (evt) { //鼠標移入box
- alert(evt.relatedTarget); //獲取移入box 最近的那個元素對象
- } //span
- box.onmouseout = function (evt) { //鼠標移出box
- alert(evt.relatedTarget); //獲取移出box 最近的那個元素對象
- } //span
IE 提供了兩組分別用於移入移出的屬性:fromElement 和toElement,分別對應mouseover
和mouseout。
- box.onmouseover = function (evt) { //鼠標移入box
- alert(window.event.fromElement.tagName); //獲取移入box 最近的那個元素對象span
- }
- box.onmouseout = function (evt) { //鼠標移入box
- alert(window.event.toElement.tagName); //獲取移入box 最近的那個元素對象span
- }
PS:fromElement 和toElement 若是分別對應相反的鼠標事件,沒有任何意義。
剩下要作的就是跨瀏覽器兼容操做:
- function getTarget(evt) {
- var e = evt || window.event; //獲得事件對象
- if (e.srcElement) { //若是支持srcElement,表示IE
- if (e.type == 'mouseover') { //若是是over
- return e.fromElement; //就使用from
- } else if (e.type == 'mouseout') { //若是是out
- return e.toElement; //就使用to
- }
- } else if (e.relatedTarget) { //若是支持relatedTarget,表示W3C
- return e.relatedTarget;
- }
- }
有時咱們須要阻止事件的默認行爲,好比:一個超連接的默認行爲就點擊而後跳轉到指
定的頁面。那麼阻止默認行爲就能夠屏蔽跳轉的這種操做,而實現自定義操做。
取消事件默認行爲還有一種不規範的作法,就是返回false。
- link.onclick = function () {
- alert('Lee');
- return false; //直接給個假,就不會跳轉了。
- };
PS:雖然return false;能夠實現這個功能,但有漏洞;第一:必須寫到最後,這樣致使
中間的代碼執行後,有可能執行不到return false;第二:return false 寫到最前那麼以後的自
定義操做就失效了。因此,最好的方法應該是在最前面就阻止默認行爲,而且後面還能執行
代碼。
- link.onclick = function (evt) {
- evt.preventDefault(); //W3C,阻止默認行爲,放哪裏均可以
- alert('Lee');
- };
- link.onclick = function (evt) { //IE,阻止默認行爲
- window.event.returnValue = false;
- alert('Lee');
- };
跨瀏覽器兼容
- function preDef(evt) {
- var e = evt || window.event;
- if (e.preventDefault) {
- e.preventDefault();
- } else {
- e.returnValue = false;
- }
- }
上下文菜單事件:contextmenu,當咱們右擊網頁的時候,會自動出現windows 自帶的
菜單。那麼咱們可使用contextmenu 事件來修改咱們指定的菜單,但前提是把右擊的默認
行爲取消掉。
- addEvent(window, 'load', function () {
- var text = document.getElementById('text');
- addEvent(text, 'contextmenu', function (evt) {
- var e = evt || window.event;
- preDef(e);
- var menu = document.getElementById('menu');
- menu.style.left = e.clientX + 'px';
- menu.style.top = e.clientY + 'px';
- menu.style.visibility = 'visible';
- addEvent(document, 'click', function () {
- document.getElementById('myMenu').style.visibility = 'hidden';
- });
- });
- });
PS:contextmenu 事件很經常使用,這直接致使瀏覽器兼容性較爲穩定。
卸載前事件:beforeunload,這個事件能夠幫助在離開本頁的時候給出相應的提示,「離
開」或者「返回」操做。
- addEvent(window, 'beforeunload', function (evt) {
- preDef(evt);
- });
鼠標滾輪(mousewheel)和DOMMouseScroll,用於獲取鼠標上下滾輪的距離。
- addEvent(document, 'mousewheel', function (evt) { //非火狐
- alert(getWD(evt));
- });
- addEvent(document, 'DOMMouseScroll', function (evt) { //火狐
- alert(getWD(evt));
- });
- function getWD(evt) {
- var e = evt || window.event;
- if (e.wheelDelta) {
- return e.wheelDelta;
- } else if (e.detail) {
- return -evt.detail * 30; //保持計算的統一
- }
- }
PS:經過瀏覽器檢測能夠肯定火狐只執行DOMMouseScroll。
DOMContentLoaded 事件和readystatechange 事件,有關DOM 加載方面的事件,關於這 兩個事件的內容很是多且繁雜,咱們先點明在這裏,在項目課程中使用的時候詳細討論。