事件(上)
JavaScript事件列表 |
事件 |
解說 |
一般事件 |
onclick |
鼠標點擊時觸發此事件 |
ondblclick |
鼠標雙擊時觸發此事件 |
onmousedown |
按下鼠標時觸發此事件 |
onmouseup |
鼠標按下后松開鼠標時觸發此事件 |
onmouseover |
當鼠標移動到某對象范圍的上方時觸發此事件 |
onmousemove |
鼠標移動時觸發此事件 |
onmouseout |
當鼠標離開某對象范圍時觸發此事件 |
onkeypress |
當鍵盤上的某個鍵被按下并且釋放時觸發此事件. |
onkeydown |
當鍵盤上某個按鍵被按下時觸發此事件 |
onkeyup |
當鍵盤上某個按鍵被按放開時觸發此事件 |
頁面相關事件 |
onabort |
圖片在下載時被用戶中斷 |
onbeforeunload |
當前頁面的內容將要被改變時觸發此事件 |
onerror |
出現錯誤時觸發此事件 |
onload |
頁面內容完成時觸發此事件 |
onmove |
瀏覽器的窗口被移動時觸發此事件 |
onresize |
當瀏覽器的窗口大小被改變時觸發此事件 |
onscroll |
瀏覽器的滾動條位置發生變化時觸發此事件 |
onstop |
瀏覽器的停止按鈕被按下時觸發此事件或者正在下載的文件被中斷 |
oncontextmenu |
當彈出右鍵上下文菜單時發生 |
onunload |
當前頁面將被改變時觸發此事件 |
表單相關事件 |
onblur |
當前元素失去焦點時觸發此事件 |
onchange |
當前元素失去焦點并且元素的內容發生改變而觸發此事件 |
onfocus |
當某個元素獲得焦點時觸發此事件 |
onreset |
當表單中RESET的屬性被激發時觸發此事件 |
onsubmit |
一個表單被遞交時觸發此事件 |
了解上面的事件如此簡單,那么事件還有什么可講的呢?
問題一:每個事件只能注冊一個函數
var oDiv = document.getElementById("oDiv");
oDiv.onclick = fn1;
oDiv.onclick =fn2;

function fn1()
{alert("我被覆蓋了!")}

function fn2()
{alert("只有我被執行到!")}
解決方案一:

obj.onclick = function ()
{
fn1();
fn2();
fn3();
};
缺陷一:需要將所有函數一次添加進去,不能在運行時添加
缺陷二:在事件處理函數中this將指向window,而不是obj
解決方案二:

function addEvent(fn,evtype,obj)
{
//obj是要添加事件的HTML元素對象
//evtype是事件名字,不包含on前綴,因為每個都有on,所以寫個on是多余的
//fn是事件處理函數
var oldFn;

if (obj["on"+evtype] instanceof Function)
{
oldFn = obj["on"+evtype];//當添加函數時,如果已注冊過了,則將其保存起來
}

obj["on"+evtype]=function ()
{

if (oldFn)
{
oldFn.call(this);
}
fn.call(this);//使用call方法,使事件處理函數中的this仍指向obj
};
}
這樣已經解決了問題,但如何刪除事件呢?如果直接將對象的onevtype這類的屬性賦值為null將會刪除所有的事件處理函數!
解決方案二的修改版:先將事件存儲起來,存儲在對象的__EventHandles屬性里面
eventHandlesCounter=1;//計數器,將統計所有添加進去的函數的個數,0位預留作其它用

function addEvent(fn,evtype,obj)
{

if (!fn.__EventID)
{//__EventID是給函數加的一個標識,見下面給函數添加標識的部分
fn.__EventID=eventHandlesCounter++;
//使用一個自動增長的計數器作為函數的標識以保證不會重復
}

if (!obj.__EventHandles)
{
obj.__EventHandles=[];//當不存在,也就是第一次執行時,創建一個,并且是數組
}

if (!obj.__EventHandles[evtype])
{//將所有事件處理函數按事件類型分類存放

obj.__EventHandles[evtype]=
{};//當不存在時也創建一個散列表<BR> if (obj["on"+evtype] instanceof Function) {
//查看是否已經注冊過其它函數
//如果已經注冊過,則將以前的事件處理函數添加到下標為0的預留的位置
obj.__EventHandles[evtype][0]=obj["on"+evtype];
obj["on"+evtype]=handleEvents;//使用handleEvents集中處理所有的函數
}
}
obj.__EventHandles[evtype][fn.__EventID]=fn;
//如果函數是第一次注冊為事件處理函數,那么它將被添加到表中,函數的標識作為下標
//如果函數已經注冊過相同對象的相同事件了,那么將覆蓋原來的而不會被添加兩次

function handleEvents()
{
var fns = obj.__EventHandles[evtype];

for (var i in fns)
{
fns[i].call(this);
}
}
}
使用上面的函數已經可以在一個對象添加多個事件處理函數,在函數內部this關鍵字也指向了相應的對象,并且這些函數都被作了標識,那么移除某個事件處理函數就是輕而易舉的了!
//使用傳統方法:obj.onevtype = null;但這樣會移除所有的事件處理函數

function delEvent(fn,evtype,obj)
{

if (!obj.__EventHandles || !obj.__EventHandles[evtype] || !fn.__EventID)
{
return false;
}

if (obj.__EventHandles[evtype][fn.__EventID] == fn)
{
delete obj.__EventHandles[evtype][fn.__EventID];
}
}
事件(下)
事件對象——Event
事件對象是用來記錄一些事件發生時的相關信息的對象。事件對象只有事件發生時才會產生,并且只能是事件處理函數內部訪問,在所有事件處理函數運行結束后,事件對象就被銷毀!
訪問事件對象:W3C DOM方法與IE專用方法
//W3C DOM把事件對象作為事件處理函數的第一個參數傳入進去

document.onclick = function (evt)
{//這樣,事件對象只能在對應的事件處理函數內部可以訪問到
alert(evt);
};
//IE將事件對象作為window對象的一個屬性(相當于全局變量)
//貌似全局對象,但是只有是事件發生時才能夠訪問
alert(window.event);//null

window.onload = function ()
{
alert(window.event);
};
事件對象的屬性及方法
鼠標相關
屬性名 |
值類型 |
讀/寫 |
描述 |
button |
Integer |
R |
對于特定的鼠標事件,表示按下的鼠標按鈕,該屬性僅可以在mouseup與mousedown事件中訪問。W3C 規定:0表示按下了左鍵,1表示按下了中鍵,2表示按下了右鍵,相當于對于鼠標鍵從左到右進行的編號,而編號從0開始; 而IE有另外一套規定:0表示沒有任何鍵按下,1表示左鍵,2表示右鍵,4表示中鍵,而其它按鍵的組合則只要將鍵碼相加即可,如:同時按下左右鍵時button值為3
|
clientX |
Integer |
R |
事件發生時,鼠標在客戶端區域的X坐標,客戶端區域是指頁面可視區域 |
clientY |
Integer |
R |
事件發生時,鼠標在客戶端區域的Y坐標 |
screenX |
Integer |
R(IE) R/W(W3C) |
相對于屏幕的鼠標X坐標 |
screenY |
Integer |
R(IE) R/W(W3C) |
相對于屏幕的鼠標Y坐標 |
x(僅IE) |
Integer |
R |
鼠標相對于引起事件的元素的父元素的X坐標 |
y(僅IE) |
Integer |
R |
鼠標相對于引起事件的元素的父元素的Y坐標 |
offsetX(僅IE) layerX(僅W3C) |
Integer |
R |
鼠標相對于引起事件的對象的X坐標 |
offsetY(僅IE) layerY(僅W3C) |
Integer |
R |
鼠標相對于引起事件的對象的Y坐標 |
pageX(僅W3C) |
Integer |
R |
鼠標相對于頁面的X坐標 |
pageY(僅W3C) |
Integer |
R |
鼠標相對于頁面的Y坐標 |
鍵盤相關
屬性名 |
值類型 |
讀/寫 |
描述 |
altKey |
Boolean |
R |
true表示按下了ALT鍵;false表示沒有按下 |
ctrlKey |
Boolean |
R |
true表示按下了CTROL,false表示沒有 |
shiftKey |
Boolean |
R |
true表示按下了shift,false表示沒有 |
keyCode |
Integer |
R/W(IE) R(W3C) |
對于keypress事件,表示按下按鈕的Unicode字符;對于keydown/keyup事件 ,表示按下按鈕的數字代號 |
charCode(僅W3C) |
Integer |
R |
在keypress事件中所按鍵的字符Unicode編碼,如果不是字符鍵,則該屬性為0,并且,當CapsLock打開與關閉時charCode的值也對應著大小寫字母 |
其它
屬性名 |
值類型 |
讀/寫 |
描述 |
srcElement(IE) target(W3C) |
Element |
R |
引起事件的元素 |
fromElement(僅IE) |
Element |
R |
某些鼠標事件中(mouseover與mouseout),鼠標所離開的元素 |
toElement(僅IE) |
Element |
R |
某些鼠標事件中(mouseover與mouseout),鼠標所進入的元素 |
relatedTarget(僅W3C) |
Element |
R |
某些鼠標事件中(mouseover與mouseout),返回與事件的目標節點相關的節點。 |
repeat(僅IE) |
Boolean |
R |
如果不斷觸發keydown事件,則為true,否則為false |
returnValue(僅IE) |
Boolean |
R/W |
將其設為false表示以取消事件的默認動作 |
preventDefault(僅W3C) |
Function |
R |
執行方法以取消事件的默認動作 |
type |
String |
R |
事件的名稱,不帶on前綴 |
cancelable(僅W3C ) |
Boolean |
R |
當為true表示事件的默認動作可以被取消(用preventDefault方法取消) |
cancelBubble(僅IE) |
Boolean |
R/W |
將其設置為true將取消事件冒泡 |
stopPropagation(僅W3C) |
Function |
R |
執行方法取消事件冒泡 |
bubbles(僅W3C) |
Boolean |
R |
返回true表示事件是冒泡類型 |
eventPhase(僅W3C) |
Integer |
R |
返回事件傳播的當前階段。它的值是下面的三個常量之一,它們分別表示捕獲階段、在目標對象上時和起泡階段:
常量 |
值 |
Event.CAPTURING_PHASE(捕獲階段) |
1 |
Event.AT_TARGET(在目標對象上) |
2 |
Event.BUBBLING_PHASE(冒泡階段) |
3 |
|
timeStamp (僅W3C) |
Long |
R |
返回一個時間戳。指示發生事件的日期和時間(從 epoch 開始的毫秒數)。epoch 是一個事件參考點。在這里,它是客戶機啟動的時間。并非所有系統都提供該信息,因此,timeStamp 屬性并非對所有系統/事件都是可用的。 |
取得事件對象及取得事件目標對象

document.onclick =function (evt)
{
evt = evt || window.event;//在IE中evt會是undefined
//而支持W3C DOM事件的瀏覽器中事件對象將會作為事件處理函數的第一個參數
var targetElement = evt.target || evt.srcElement;
//IE中事件對象沒有target屬性
};
阻止事件發生時瀏覽器的默認行為

document.onclick = function (evt)
{
evt = evt || window.event;
var target = evt.target || evt.srcElement;

if (!target)
{
return;
}

if (target.tagName=="A" && target.href)
{
//使用傳統的方法取消事件默認行為必須使用return false
//但使用了return ,函數便終止了運行,可以使用事件對象來取消

if (window.event)
{//IE
window.event.returnValue = false;

} else
{
evt.preventDefault();
}
window.open(target.href,"newWindow");
//這樣讓所有的鏈接在新窗口打開
}
};
事件傳播——冒泡與捕獲
DOM事件標準定義了兩種事件流,這兩種事件流有著顯著的不同并且可能對你的應用有著相當大的影響。這兩種事件流分別是捕獲和冒泡。和許多Web技術一樣,在它們成為標準之前,Netscape和微軟各自不同地實現了它們。Netscape選擇實現了捕獲事件流,微軟則實現了冒泡事件流。幸運的是,W3C決定組合使用這兩種方法,并且大多數新瀏覽器都遵循這兩種事件流方式。
默認情況下,事件使用冒泡事件流,不使用捕獲事件流。然而,在Firefox和Safari里,你可以顯式的指定使用捕獲事件流,方法是在注冊事件時傳入useCapture參數,將這個參數設為true。
冒泡事件流
當事件在某一DOM元素被觸發時,例如用戶在客戶名字節點上點擊鼠標,事件將跟隨著該節點繼承自的各個父節點冒泡穿過整個的DOM節點層次,直到它遇到依附有該事件類型處理器的節點,此時,該事件是onclick事件。在冒泡過程中的任何時候都可以終止事件的冒泡,在遵從W3C標準的瀏覽器里可以通過調用事件對象上的stopPropagation()方法,在Internet Explorer里可以通過設置事件對象的cancelBubble屬性為true。如果不停止事件的傳播,事件將一直通過DOM冒泡直至到達文檔根。
捕獲事件流
事件的處理將從DOM層次的根開始,而不是從觸發事件的目標元素開始,事件被從目標元素的所有祖先元素依次往下傳遞。在這個過程中,事件會被從文檔根到事件目標元素之間各個繼承派生的元素所捕獲,如果事件監聽器在被注冊時設置了useCapture屬性為true,那么它們可以被分派給這期間的任何元素以對事件做出處理;否則,事件會被接著傳遞給派生元素路徑上的下一元素,直至目標元素。事件到達目標元素后,它會接著通過DOM節點再進行冒泡。
現代事件綁定方法
針對如上節課所討論的,使用傳統事件綁定有許多缺陷,比如不能在一個對象的相同事件上注冊多個事件處理函數。而瀏覽器和W3C也并非沒有考慮到這一點,因此在現代瀏覽器中,它們有自己的方法綁定事件。
W3C DOM
- obj.addEventListener(evtype,fn,useCapture)——W3C提供的添加事件處理函數的方法。obj是要添加事件的對象,evtype是事件類型,不帶on前綴,fn是事件處理函數,如果useCapture是true,則事件處理函數在捕獲階段被執行,否則在冒泡階段執行
- obj.removeEventListener(evtype,fn,useCapture)——W3C提供的刪除事件處理函數的方法
微軟IE方法
- obj.attachEvent(evtype,fn)——IE提供的添加事件處理函數的方法。obj是要添加事件的對象,evtype是事件類型,帶on前綴,fn是事件處理函數,IE不支持事件捕獲
- obj.detachEvent(evtype,fn,)——IE提供的刪除事件處理函數的方法,evtype包含on前綴
整合兩者的方法

function addEvent(obj,evtype,fn,useCapture)
{

if (obj.addEventListener)
{
obj.addEventListener(evtype,fn,useCapture);

} else
{
obj.attachEvent("on"+evtype,fn);//IE不支持事件捕獲

} else
{
obj["on"+evtype]=fn;//事實上這種情況不會存在
}
}

function delEvent(obj,evtype,fn,useCapture)
{

if (obj.removeEventListener)
{
obj.removeEventListener(evtype,fn,useCapture);

} else
{
obj.detachEvent("on"+evtype,fn);

} else
{
obj["on"+evtype]=null;
}
}
其它兼容性問題:IE不支持事件捕獲?很抱歉,這個沒有辦法解決!但IE的attach方法有個問題,就是使用attachEvent時在事件處理函數內部,this指向了window,而不是obj!當然,這個是有解決方案的!

function addEvent(obj,evtype,fn,useCapture)
{

if (obj.addEventListener)
{
obj.addEventListener(evtype,fn,useCapture);

} else
{

obj.attachEvent("on"+evtype,function ()
{
fn.call(obj);
});

} else
{
obj["on"+evtype]=fn;//事實上這種情況不會存在
}
}
但IE的attachEvent方法有另外一個問題,同一個函數可以被注冊到同一個對象同一個事件上多次,解決方法:拋棄IE的 attachEvent方法吧!IE下的attachEvent方法不支持捕獲,和傳統事件注冊沒多大區別(除了能綁定多個事件處理函數),并且IE的 attachEvent方法存在內存泄漏問題!
addEvent,delEvent現代版

function addEvent(obj,evtype,fn,useCapture)
{

if (obj.addEventListener)
{//優先考慮W3C事件注冊方案
obj.addEventListener(evtype,fn,!!useCapture);

} else
{//當不支持addEventListener時(IE),由于IE同時也不支持捕獲,所以不如使用傳統事件綁定

if (!fn.__EventID)
{fn.__EventID = addEvent.__EventHandlesCounter++;}
//為每個事件處理函數分配一個唯一的ID

if (!obj.__EventHandles)
{obj.__EventHandles=
{};}
//__EventHandles屬性用來保存所有事件處理函數的引用
//按事件類型分類

if (!obj.__EventHandles[evtype])
{//第一次注冊某事件時

obj.__EventHandles[evtype]=
{};

if (obj["on"+evtype])
{//以前曾用傳統方式注冊過事件處理函數
(obj.__EventHandles[evtype][0]=obj["on"+evtype]).__EventID=0;//添加到預留的0位
//并且給原來的事件處理函數增加一個ID
}
obj["on"+evtype]=addEvent.execEventHandles;
//當事件發生時,execEventHandles遍歷表obj.__EventHandles[evtype]并執行其中的函數
}
}
}
addEvent.__EventHandlesCounter=1;//計數器,0位預留它用

addEvent.execEventHandles = function (evt)
{//遍歷所有的事件處理函數并執行

if (!this.__EventHandles)
{return true;}
evt = evt || window.event;
var fns = this.__EventHandles[evt.type];

for (var i in fns)
{
fns[i].call(this);
}
};

function delEvent(obj,evtype,fn,useCapture)
{

if (obj.removeEventListener)
{//先使用W3C的方法移除事件處理函數
obj.removeEventListener(evtype,fn,!!useCapture);

} else
{

if (obj.__EventHandles)
{
var fns = obj.__EventHandles[evtype];

if (fns)
{delete fns[fn.__EventID];}
}
}
}
標準化事件對象
IE的事件對象與W3C DOM的事件對象有許多不一樣的地方,解決的最好的方法就是調整IE的事件對象,以使它盡可能的與標準相似!下表列出了IE事件對象中一些和W3C DOM名稱或值不一樣但含義相同的屬性
IE與W3C DOM事件對象的不同
W3C DOM |
IE |
button——按鍵編碼為:0-左鍵,1-中鍵,2-右鍵 |
button——按鍵編碼為:1-左鍵,2-右鍵,4-中鍵 |
charCode |
沒有對應屬性,但可以用keyCode來代替 |
preventDefault |
沒有對應方法,但可以將event對象的returnValue設為false來模擬 |
target |
srcElement |
relatedTarget |
fromElement與toElement |
stopPropagation |
沒有對應方法,但可以通過將event對象的cancelBubble屬性設為true來模擬 |
總結出fixEvent函數

function fixEvent(evt)
{

if (!evt.target)
{
evt.target = evt.srcElement;
evt.preventDefault = fixEvent.preventDefault;
evt.stopPropagation = fixEvent.stopPropagation;

if (evt.type == "mouseover")
{
evt.relatedTarget = evt.fromElement;

} else if (evt.type =="mouseout")
{
evt.relatedTarget = evt.toElement;
}
evt.charCode = (evt.type=="keypress")?evt.keyCode:0;
evt.eventPhase = 2;//IE僅工作在冒泡階段
evt.timeStamp = (new Date()).getTime();//僅將其設為當前時間
}
return evt;
}

fixEvent.preventDefault =function ()
{
this.returnValue = false;//這里的this指向了某個事件對象,而不是fixEvent
};

fixEvent.stopPropagation =function ()
{
this.cancelBubble = true;
};
fixEvent函數不是單獨執行的,它必須有一個事件對象參數,而且只有事件發生時它才被執行!最好的方法是把它整合到addEvent函數的execEventHandles里面

addEvent.execEventHandles = function (evt)
{//遍歷所有的事件處理函數并執行

if (!this.__EventHandles)
{return true;}
evt = fixEvent(evt || window.event);//在這里對其進行標準化操作
var fns = this.__EventHandles[evt.type];

for (var i in fns)
{
fns[i].call(this,evt);//并且將其作為事件處理函數的第一個參數
//這樣在事件處理函數內部就可以使用統一的方法訪問事件對象了
}
};
Load事件
使用JavaScript操縱DOM,必須等待DOM加載完畢才可以執行代碼,但window.onload有個壞處,它非要等到頁面中的所有圖片及視頻加載完畢才會觸發load事件。結果就是一些本來應該在打開時隱藏起來的元素,由于網絡延遲,在頁面打開時仍然會出現,然后又會突然消失,讓用戶覺得莫名其妙。
大師們想出來的方法:

function addLoadEvent(fn)
{

var init = function()
{
if (arguments.callee.done) return;
arguments.callee.done = true;
fn.apply(document,arguments);
};
//注冊DOMContentLoaded事件,如果支持的話

if (document.addEventListener)
{
document.addEventListener("DOMContentLoaded", init, false);
}
//但對于Safari,我們需要使用setInterval方法不斷檢測document.readyState
//當為loaded或complete的時候表明DOM已經加載完畢

if (/WebKit/i.test(navigator.userAgent))
{

var _timer = setInterval(function()
{

if (/loaded|complete/.test(document.readyState))
{
clearInterval(_timer);
init();
}
},10);
}
//對于IE則使用條件注釋,并使用script標簽的defer屬性
//IE中可以給script標簽添加一個defer(延遲)屬性,這樣,標簽中的腳本只有當DOM加載完畢后才執行

/**//*@cc_on @*/

/**//*@if (@_win32)
document.write("<script id=\"__ie_onload\" defer=\"defer\" src=\"javascript:void(0)\"><\/script>");
var script = document.getElementById("__ie_onload");
script.onreadystatechange = function() {
if (this.readyState == "complete") {
init();
}
};
/*@end @*/
return true;
}
from :http://www.javaeye.com/topic/517899