JS事件綁定及深入


學習要點:

1.傳統事件綁定的問題
2.W3C事件處理函數
3.IE事件處理函數
4.事件對象的其他補充

事件綁定分為兩種:一種是傳統事件綁定(內聯模型,腳本模型),一種是現代事件綁定(DOM2級模型)。現代事件綁定在傳統綁定上提供了更強大更方便的功能。

一 傳統事件綁定的問題

傳統事件綁定中的內聯模型不做討論,基本很少去用。先來看一下腳本模型,腳本模型將一個函數賦值給一個事件處理函數。傳統綁定如:

window.onload=function(){
    var box=document.getElementById('box');
    box.onclick = function(){
        alert('Lee');
    };
};

問題一:一個事件處理函數觸發兩次事件

如果一個頁面有兩個或者多個js,並且第一個js是第一個程序開發的,第二個js是第二個程序員開發的。第一個window.onload被覆蓋了,如

window.onload=function(){
    alert('Lee');
};

window.onload=function(){
    alert('Mr.lee');
}  

結果只是打印了 Mr.lee

其實是有辦法解決這個問題的,看下面這兩種形式。
a:

alert(window.onload);//一開始沒有注冊window.onload,那么就是null

window.onload=function(){
    alert('Lee');
};

alert(window.onload);//如果已經有window.onload,打印的是函數function

window.onload=function(){
    alert('Mr.lee');
} 

b:

alert(typeof window.onload);//一開始沒有window.onolad,舊版火狐顯示undefined,新版顯示object,

window.onload=function(){
    alert('Lee');
};

alert(typeof window.onload);//如果已經有window.onload,所有瀏覽器都會顯示function

window.onload=function(){
    alert('Mr.lee');
}

所以解決辦法有了。

   window.onload=function(){
    alert('Lee');
};

if(typeof window.onload=='function'){
    var saved=null;//保存上一個事件對象
    saved=window.onload;
}

//saved 就是window.onload,saved()相當於window.onload(),但是window.onload()不能執行的
//所以saved()相當於window.onload=function(){}

window.onload=function(){
    if(saved){
        saved();//執行上一個事件 window.onload=function(){}
    }
    alert('Mr.lee'); //執行本事件
}

問題二:事件切換器
切換一個id為box的div,讓里面的背景red與blue直接切換,並且切換之前彈框一次,如:

window.onload=function(){
    var box=document.getElementById('box');
    box.className="red";
    box.onclick=function(){
        alert('Lee'); //只執行了一次
        blue.call(this);//通過匿名函數執行某一函數,那么里面的this就是代表的window,所以可以通過call傳遞
    };
}

function blue(){
    this.className="blue";
    this.onclick=red;
    
}

function red(){
    this.className="red";
    this.onclick=blue;
}

上面的代碼雖然實現了切換功能,但是彈框只執行了一次。

//添加事件函數
//obj相當於window
//type相當於onload
//fn相當於function(){}
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);
    }
    
}
addEvent(window,'load',function(){
    var box=document.getElementById("box");
    //addEvent(box,'click',function(){ //目的達到,每次都執行了,沒有被覆蓋
    //    alert('ss');
    //});
    addEvent(box,'click',blue);
});

function red(){
    this.className="red";
    addEvent(box,'click',blue);
}

function blue(){
    this.className="blue";
    addEvent(box,'click',red);
}

//當不停的切換的時候,瀏覽器突然卡死,並且報錯:too much recursion,太多的遞歸
//因為積累了太多的保存的事件
//解決方案,就是用完的事件,就立刻移除掉

按照上面的代碼出現了注釋中的錯誤,解決的辦法如下:

//添加事件函數
//obj相當於window
//type相當於onload
//fn相當於function(){}
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);
    }
    
}

//當不停的切換的時候,瀏覽器突然卡死,並且報錯:too much recursion,太多的遞歸
//因為積累了太多的保存的事件
//解決方案,就是用完的事件,就立刻移除掉

//移除事件函數
function removeEvent(obj,type){
    if(obj['on'+type]){
        obj['on'+type]=null;
    }
}

addEvent(window,'load',function(){
    var box=document.getElementById("box");
    //addEvent(box,'click',function(){ //目的達到,每次都執行了,沒有被覆蓋
    //    alert('ss');
    //});
    addEvent(box,'click',blue);
});

function red(){
    this.className="red";
    removeEvent(this,'click');
    addEvent(box,'click',blue);
}

function blue(){
    this.className="blue";
    removeEvent(this,'click');
    addEvent(box,'click',red);
}

二 W3C事件處理函數

addEventListener()與removeEventListener()

W3C事件處理函數兩個,addEventListener()removeEventListener()

//W3C自帶的兩個添加事件和刪除事件

1.覆蓋問題,解決

window.addEventListener('load',function(){
    alert('Lee');
},false);

window.addEventListener('load',function(){
    alert('Mr.Lee');
},false);

window.addEventListener('load',function(){
    alert('Mrs.Lee');
},false);

2.相同函數屏蔽的問題,解決

window.addEventListener('load',init,false);
window.addEventListener('load',init,false);
window.addEventListener('load',init,false);
function init(){
    alert('Lee');
}

3.是否可以傳遞this,解決
例子1:

window.addEventListener('load',function(){
    var box=document.getElementById('box');
    box.addEventListener('click',function(){
        alert(this);
    },false);
},false);

例子2:

window.addEventListener('load',function(){
    var box=document.getElementById('box');
    box.addEventListener('click',blue,false);
},false);

function red(){
    this.className="red";
    this.removeEventListener('click',red,false);
    this.addEventListener('click',blue,false);
}

function blue(){
    this.className="blue";
    this.removeEventListener('click',blue,false);
    this.addEventListener('click',red,false);
}

4.添加一個額外的方法,會不會被覆蓋,或者只能執行一次,解決

window.addEventListener('load',function(){
    var box=document.getElementById('box');
    box.addEventListener('click',function(){
        alert('Lee');
    },false);
    box.addEventListener('click',blue,false);
},false);

綜上所述:W3C是比較完美的解決了這些問題,非常好用,但是IE8和之前的瀏覽器並不支持,而是采用了自己的事件,當然IE9已經完全支持了W3C的這兩個事件處理函數。

W3C可以設置冒泡和捕獲方式。

支持W3C標准的瀏覽器在添加事件時用addEventListener(event,fn,useCapture)方法,基中第3個參數useCapture是一個Boolean值,用來設置事件是在事件捕獲時執行,還是事件冒泡時執行。而不兼容W3C的瀏覽器(IE)用attachEvent()方法,此方法沒有相關設置,不過IE的事件模型默認是在事件冒泡時執行的,也就是在useCapture等於false的時候執行,所以把在處理事件時把useCapture設置為false是比較安全,也實現兼容瀏覽器的效果。

事件捕獲階段:事件從最上一級標簽開始往下查找,直到捕獲到事件目標(target)。
事件冒泡階段:事件從事件目標(target)開始,往上冒泡直到頁面的最上一級標簽。


事件的傳播是可以阻止的:
在W3c中,使用stopPropagation()方法
在IE下設置cancelBubble = true

三.IE事件處理函數

attachEvent()detachEvent()

IE實現了與DOM中類似的兩個方法:attachEvent()detachEvent()。這兩個方法接受相同的參數:事件名稱和函數。

在使用這兩組函數的時候,先把區別說一下:

  • 1.IE不支持捕獲,只支持冒泡
  • 2.IE添加事件不能屏蔽重復的函數
  • 3.IE中的this指向的是window而不是DOM對象
  • 4.在傳統事件上,IE是無法接受到event對象的,但使用了attchEvent卻可以,但有些區別。

1.覆蓋問題,解決了,但有不同,結果是Mrs.Lee,Mr.Lee,最后是Lee

window.attachEvent('onload',function(){
    alert('Lee');
});

window.attachEvent('onload',function(){
    alert('Mr.Lee');
});
window.attachEvent('onload',function(){
    alert('Mrs.Lee');
});

2.相同函數屏蔽的問題,未解決。

window.attachEvent('onload',init);
window.attachEvent('onload',init);

function init(){
    alert('Lee');
}

3.是否可以傳遞this,不能,this指的是window。需要用call方法。

window.attachEvent('onload',function(){
    var box=document.getElementById('box');
    box.attachEvent('onclick',function(){
        //alert(this===box);
        alert(this===window);  //true
    });
});

下面還有辦法就是通過window.event.srcElement。代碼如下:

window.attachEvent('onload',function(){
    var box=document.getElementById('box');
    box.attachEvent('onclick',blue);
});

function red(){
    var that=window.event.srcElement;
    that.className="red";
    that.detachEvent('onclick',red);
    that.attachEvent('onclick',blue);
}

function blue(){
    var that=window.event.srcElement;
    that.className="blue";
    that.detachEvent('onclick',blue);
    that.attachEvent('onclick',red);
}

4.添加一個額外的方法,會不會被覆蓋,或者只能執行一次,解決。

在傳統綁定上,IE是無法像W3C那樣通過傳參接受event對象,但是使用attachEvent()卻可以。

 window.attachEvent('onload',function(){
    var box=document.getElementById('box');
    box.onclick=function(evt){ //傳統方法IE無法通過參數獲取evt
        alert(evt);//undefined
    }
    box.attachEvent('onclick',function(evt){
        alert(evt);//object
        alert(evt.type);//click
        alert(evt.srcElement.tagName);//DIV
        alert(window.event.srcElement.tagName);//DIV
    });
});

跨瀏覽器的兼容

跨瀏覽器添加事件

function addEvent(obj,type,fn){
    if(obj.addEventListener){
        obj.addEventListener(type,fn,false);
    }else if(obj.attachEvent){
        obj.attachEvent('on'+type,fn);
    }
}

跨瀏覽器移除事件

function removeEvent(obj,type,fn){
    if(obj.removeEventListener){
        obj.removeEventListener(type,fn,false);
    }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;
    }
}

調用方式:

addEvent(window,'load',function(){
    var box=document.getElementById('box');
    addEvent(box,'click',blue);
});


function red(evt){
    var that=getTarget(evt);
    that.className="red";
    removeEvent(that,'click',red);
    addEvent(that,'click',blue);
}

function blue(evt){
    var that=getTarget(evt);
    that.className="blue";
    removeEvent(that,'click',blue);
    addEvent(that,'click',red);
}

四.事件對象的其他補充

relatedTarget事件

w3c中的一個relatedTarget事件。
例如:

addEvent(window,'load',function(){
    var box=document.getElementById('box');
    addEvent(box,'mouseover',function(evt){
        alert(evt.relatedTarget); //得到移入box最近的那個DOM對象
    });
    
    addEvent(box,'mouseout',function(evt){
        alert(evt.relatedTarget); //從box移出最近的那個DOM對象
    });
});

IE提供了兩組分別用於移入移出的屬性fromElementtoElement,分別對應mouseovermouseout

addEvent(window,'load',function(){
    var box=document.getElementById('box');
    addEvent(box,'mouseover',function(){
        alert(window.event.fromElement.tagName); //得到移入box最近的那個DOM對象
    });
    
    addEvent(box,'mouseout',function(){
        alert(window.event.toElement.tagName); //從box移出最近的那個DOM對象
    });
});

PS:fromElementtoElement如果分別對應相反的鼠標事件,沒有任何意義。

剩下要做的就是跨瀏覽器兼容操作:

function getTarget(evt){
    var e=evt || window.event;
    if(e.srcElment){ //IE
        if(e.type=='mouseover'){
            return e.fromElement.tagName;
        }else if(e.type="mouseout"){
            return e.toElement.tagName;
        }
    }else if(e.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){
    window.event.returnValue=false;//IE,阻止默認行為
    alert('Lee');
}

那么跨瀏覽器的兼容:

function preDef(evt){
    var e=evt || window.event;
    if(e.preventDefault){
        e.preventDefault();
    }else{
        e.returnValue=false;
    }
}

右鍵菜單contextmenu

兼容:

function preDef(evt){
    var e=evt || window.event;
    if(e.preventDefault){
        e.preventDefault();
    }else{
        e.returnValue=false;
    }
}

addEvent(window,"load",function(){
    var body=document.getElementsByTagName('body')[0];
    addEvent(body,'contextmenu',function(evt){
        preDef(evt);
    })
});

PS:contextmenu事件很常用,這直接導致瀏覽器兼容性較為穩定。

卸載前事件:beforeunload

這個事件可以幫助在離開本頁的時候給出相應的提示,“離開”或者“返回”操作。

addEvent(window,'beforeonload',function(){
    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事件

DOMContentLoaded事件和readystatechange事件,有關DOM加載方面的事件。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM