JavaScript Event學(xué)習(xí)第八章 事件的順序
更新時(shí)間:2010年02月07日 10:47:20 作者:
在第一章中我提到一個(gè)初次看起來可能不是那么好理解的是一個(gè)問題:“如果一個(gè)元素和他的父元素對于同樣的事件都有事件處理程序,那么哪個(gè)會首先執(zhí)行呢?”毫無疑問,看是什么瀏覽器。
基本問題很簡單。假設(shè)你的一個(gè)元素包含在另外一個(gè)元素中。
-----------------------------------
| element1 |
| ------------------------- |
| |element2 | |
| ------------------------- |
-----------------------------------
這兩個(gè)元素都有onclick事件處理程序。如果用戶在element2上面單擊那么在元素2和元素1上都觸發(fā)了單擊事件。但是哪個(gè)事件先發(fā)生呢?哪個(gè)事件處理程序會先執(zhí)行呢?換句話說,事件順序(event order)是什么呢?
兩種模式
毫無疑問的,Netscape和微軟在過去那段很糟糕的日子里都做出了自己的決定。
Netscape說element1先發(fā)生的。這叫事件捕獲(event capturing)。
微軟覺得element2先發(fā)生的。這叫事件冒泡(event bubbling)。
這兩種事件順序剛好相反。IE只支持事件冒泡。Mozilla,Opera 7和Konqueror兩種都支持。早一些的Opear和iCab瀏覽器兩個(gè)都不支持。
事件捕獲
當(dāng)你使用事件捕獲的時(shí)候
---------------| |-----------------
| element1 | | |
| --------- --| |----------- |
| |element2 \ / | |
| ------------------------- |
| Event CAPTURING |
-----------------------------------
element1的事件處理程序會先執(zhí)行,element2后執(zhí)行。
事件冒泡
但你使用事件冒泡的時(shí)候
/ \
---------------| |-----------------
| element1 | | |
| ---------- -| |----------- |
| |element2 | | | |
| ------------------------- |
| Event BUBBLING |
-----------------------------------
element2的事件處理程序會先執(zhí)行,element1的事件處理程序后執(zhí)行。
W3C模式
W3C決定在這場戰(zhàn)爭中保持重力。在W3C事件模型中任何事件發(fā)生都是首先被捕獲直到到達(dá)目標(biāo)元素,然后再冒泡。
| | / \
-----------------| |--| |-----------------
| element1 | | | | |
| ----------- --| |--| |----------- |
| |element2 \ / | | | |
| -------------------------------- |
| W3C event model |
------------------------------------------
作為設(shè)計(jì)師的你,可以隨意選擇把事件處理程序注冊在捕獲還是冒泡階段。通過之前高級模式里面介紹的addEventListener()方法就可以完成。如果最后一個(gè)參數(shù)是true那么就設(shè)置成為事件捕獲,如果是false就設(shè)置為事件冒泡。
假設(shè)你這樣寫
element1.addEventListener('click',doSomething2,true)
element2.addEventListener('click',doSomething,false)
如果用戶在element2上單擊就會發(fā)生下面的事情:
、click事件發(fā)生在捕獲階段。這樣看來,如果element2的任何一個(gè)父元素有onclick事件處理程序那么都會執(zhí)行。
、事件在element1上發(fā)現(xiàn)了doSomething2(),那么就會執(zhí)行它。
、事件向下傳遞直到目標(biāo)本身,再沒有其他的捕獲階段程序了。事件轉(zhuǎn)而進(jìn)入冒泡階段然后就會執(zhí)行doSomething(),也就是element2注冊在冒泡階段的事件處理程序。
、事件再向上傳遞再檢查是否有父元素在冒泡階段設(shè)置事件處理程序。這里沒有,所以什么也不會發(fā)生。
反過來:
element1.addEventListener('click',doSomething2,false)
element2.addEventListener('click',doSomething,false)
現(xiàn)在如果用戶在element2上面點(diǎn)擊就會發(fā)生:
、事件click發(fā)生在捕獲階段。事件會查找element2的父元素是否有在捕獲階段注冊事件處理程序,在這里沒有。
、事件向下傳遞直到目標(biāo)本身。然后開始冒泡階段,執(zhí)行dosomething(),這個(gè)是注冊在element2冒泡階段的事件處理程序。
、事件繼續(xù)向上傳遞然后檢查是否有父元素在冒泡階段注冊了事件處理程序。
、事件發(fā)現(xiàn)了element1.然后doSomething2()就被執(zhí)行了。
傳統(tǒng)模式下的兼容性
對于那些支持W3C DOM的瀏覽器來說,傳統(tǒng)的事件注冊
element1.onclick = doSomething2;
就被看做是注冊在冒泡階段的。
事件冒泡的使用
很少有設(shè)計(jì)師意識到事件捕獲或者冒泡。在網(wǎng)頁制作的今天,貌似沒必要讓一個(gè)冒泡事件被一系列的事件處理程序來處理。用戶也會在單擊之后發(fā)生一系列事件而感到迷惑,通常你也想讓你的事件處理程序的代碼保持一定的獨(dú)立性。當(dāng)用戶點(diǎn)擊一個(gè)元素,發(fā)生了一些事情,當(dāng)他單擊其他元素,那么其他再發(fā)生其他事情。
當(dāng)然在將來也許會改變,最好讓模式向前兼容。但是如今最實(shí)用的事件捕獲和冒泡就是默認(rèn)函數(shù)的注冊。
它總是會發(fā)生
首先你需要理解的就是事件捕獲或者冒泡總是在發(fā)生的。如果你為你的整個(gè)頁面定義了一個(gè)onclick事件:
document.onclick = doSomething;
if (document.captureEvents) document.captureEvents(Event.CLICK);
你在任意元素上的click時(shí)間都會冒泡到頁面然后出發(fā)了這個(gè)事件處理程序。只有當(dāng)前面的事件處理程序明確的阻止冒泡,才不會傳遞到整個(gè)頁面。
使用
因?yàn)槊總€(gè)事件都會在整個(gè)文檔上停止,默認(rèn)的事件處理程序就變得可能。假設(shè)你有一個(gè)這樣的頁面:
------------------------------------
| document |
| --------------- ------------ |
| | element1 | | element2 | |
| --------------- ------------ |
------------------------------------
element1.onclick = doSomething;
element2.onclick = doSomething;
document.onclick = defaultFunction;
現(xiàn)在如果用戶點(diǎn)擊了element1或者element2那么doSomething()就會執(zhí)行。在這如果你愿意也可以阻止他的傳播。如果不的話,那么defaultFunction()就會執(zhí)行。用戶在其他地方的點(diǎn)擊也會讓defaultFunction()執(zhí)行。有時(shí)候這可能有用。
設(shè)置全局的事件處理程序在寫拖動(dòng)代碼的時(shí)候就很有必要。通常一個(gè)層上的mousedow事件會選擇這個(gè)層然后對mousemove事件做出回應(yīng)。雖然mousedown通常注冊在這個(gè)層上來避免一些瀏覽器的bug,但是其他的事件處理程序都必須是全局的(document-wide)。
記住瀏覽器邏輯的第一定律:什么都會發(fā)生的,而且經(jīng)常是在你做的準(zhǔn)備最少的時(shí)候??赡馨l(fā)生的是用戶的鼠標(biāo)瘋狂的移動(dòng)然后代碼沒有跟上導(dǎo)致鼠標(biāo)已經(jīng)不再這個(gè)層上了。
如果在某個(gè)層上注冊了onmousemove事件處理程序,如果這個(gè)層不再響應(yīng)鼠標(biāo)的移動(dòng)了,那么肯定會讓用戶感到迷惑。
如果某個(gè)曾上注冊了onmouseup事件處理程序,那么程序會在用戶松開鼠標(biāo)的時(shí)候程序沒有捕捉到造成這個(gè)層還在隨著鼠標(biāo)移動(dòng)。
在這種情況下事件冒泡就很重要,因?yàn)槿值氖录幚沓绦驎WC執(zhí)行的。
關(guān)掉它
但是通常你想關(guān)閉所有的相關(guān)的捕獲和冒泡。另外,如果你的文檔結(jié)構(gòu)非常的復(fù)雜(比如一大堆復(fù)雜的表格之類)你也需要關(guān)閉冒泡來節(jié)省系統(tǒng)資源。要不然瀏覽器就得一個(gè)個(gè)的查看父元素是否有事件處理程序。雖然可能一個(gè)都沒有,但是查找一樣浪費(fèi)時(shí)間。
在微軟模式里你必須講事件的cancelBubble屬性設(shè)置為true。
window.event.cancelBubble = true
在W3C模式中你必須調(diào)用stopPropagation()方法。
e.stopPropagation()
這會阻止這個(gè)事件的冒泡階段。阻止事件的捕獲極端基本上是不可能的。我也想知道為啥。
一個(gè)完整的跨瀏覽器的代碼如下:
function doSomething(e)
{
if (!e) var e = window.event;
e.cancelBubble = true;
if (e.stopPropagation) e.stopPropagation();
}
在不支持cancelBubble的瀏覽器里面設(shè)置也不會有啥問題。瀏覽器會創(chuàng)建一個(gè)這樣的屬性。當(dāng)然是沒啥用的,只是為了安全。
currentTarget
我們之前講過,一個(gè)事件包含target或者srcElement包含一個(gè)發(fā)生事件的元素的引用。在我們的例子是element2,因?yàn)橛脩酎c(diǎn)擊了他。
理解在捕獲和冒泡過程中這個(gè)target是不會改變的非常重要:他一直指向element2.
但是假設(shè)我們注冊了下面的事件處理程序:
element1.onclick = doSomething;
element2.onclick = doSomething;
如果用戶點(diǎn)擊element2那么doSomething()執(zhí)行了兩次。那么你怎樣知道那個(gè)HTML元素處理著這個(gè)事件呢?target/scrElement也不能給出答案,從事件一開始就一直指向element2.
為了解決這個(gè)問題W3C添加了currentTarget屬性。它包含一個(gè)正在處理的事件的HTML元素的引用:就是我們想要的那個(gè)。不幸的是微軟模式?jīng)]有類似的屬性。
你也可以用this關(guān)鍵字。在這個(gè)例子里就是指向正在處理的事件的HTML元素,就先currentTarget。
微軟模式的問題
但是當(dāng)你使用微軟的事件注冊模型,this關(guān)鍵字不是只想HTML元素的。然后又沒有一個(gè)像currentTarget類似的屬性,這就意味著如果你這么做:
element1.attachEvent('onclick',doSomething)
element2.attachEvent('onclick',doSomething)
你就不知道那個(gè)HTML元素正在處理事件。這是微軟的事件注冊模式最嚴(yán)重的問題所以就根本不要用他,即使是那些只在IE/win下的程序。
我希望微軟能夠盡快添加一個(gè)類似currentTarget的屬性或者遵循標(biāo)準(zhǔn)?我們設(shè)計(jì)急需啊。
繼續(xù)
如果你想繼續(xù)學(xué)習(xí),請看下一章。
原文地址:http://www.quirksmode.org/js/events_order.html
我的twitter: @rehawk
復(fù)制代碼 代碼如下:
-----------------------------------
| element1 |
| ------------------------- |
| |element2 | |
| ------------------------- |
-----------------------------------
這兩個(gè)元素都有onclick事件處理程序。如果用戶在element2上面單擊那么在元素2和元素1上都觸發(fā)了單擊事件。但是哪個(gè)事件先發(fā)生呢?哪個(gè)事件處理程序會先執(zhí)行呢?換句話說,事件順序(event order)是什么呢?
兩種模式
毫無疑問的,Netscape和微軟在過去那段很糟糕的日子里都做出了自己的決定。
Netscape說element1先發(fā)生的。這叫事件捕獲(event capturing)。
微軟覺得element2先發(fā)生的。這叫事件冒泡(event bubbling)。
這兩種事件順序剛好相反。IE只支持事件冒泡。Mozilla,Opera 7和Konqueror兩種都支持。早一些的Opear和iCab瀏覽器兩個(gè)都不支持。
事件捕獲
當(dāng)你使用事件捕獲的時(shí)候
復(fù)制代碼 代碼如下:
---------------| |-----------------
| element1 | | |
| --------- --| |----------- |
| |element2 \ / | |
| ------------------------- |
| Event CAPTURING |
-----------------------------------
element1的事件處理程序會先執(zhí)行,element2后執(zhí)行。
事件冒泡
但你使用事件冒泡的時(shí)候
復(fù)制代碼 代碼如下:
/ \
---------------| |-----------------
| element1 | | |
| ---------- -| |----------- |
| |element2 | | | |
| ------------------------- |
| Event BUBBLING |
-----------------------------------
element2的事件處理程序會先執(zhí)行,element1的事件處理程序后執(zhí)行。
W3C模式
W3C決定在這場戰(zhàn)爭中保持重力。在W3C事件模型中任何事件發(fā)生都是首先被捕獲直到到達(dá)目標(biāo)元素,然后再冒泡。
復(fù)制代碼 代碼如下:
| | / \
-----------------| |--| |-----------------
| element1 | | | | |
| ----------- --| |--| |----------- |
| |element2 \ / | | | |
| -------------------------------- |
| W3C event model |
------------------------------------------
作為設(shè)計(jì)師的你,可以隨意選擇把事件處理程序注冊在捕獲還是冒泡階段。通過之前高級模式里面介紹的addEventListener()方法就可以完成。如果最后一個(gè)參數(shù)是true那么就設(shè)置成為事件捕獲,如果是false就設(shè)置為事件冒泡。
假設(shè)你這樣寫
element1.addEventListener('click',doSomething2,true)
element2.addEventListener('click',doSomething,false)
如果用戶在element2上單擊就會發(fā)生下面的事情:
、click事件發(fā)生在捕獲階段。這樣看來,如果element2的任何一個(gè)父元素有onclick事件處理程序那么都會執(zhí)行。
、事件在element1上發(fā)現(xiàn)了doSomething2(),那么就會執(zhí)行它。
、事件向下傳遞直到目標(biāo)本身,再沒有其他的捕獲階段程序了。事件轉(zhuǎn)而進(jìn)入冒泡階段然后就會執(zhí)行doSomething(),也就是element2注冊在冒泡階段的事件處理程序。
、事件再向上傳遞再檢查是否有父元素在冒泡階段設(shè)置事件處理程序。這里沒有,所以什么也不會發(fā)生。
反過來:
element1.addEventListener('click',doSomething2,false)
element2.addEventListener('click',doSomething,false)
現(xiàn)在如果用戶在element2上面點(diǎn)擊就會發(fā)生:
、事件click發(fā)生在捕獲階段。事件會查找element2的父元素是否有在捕獲階段注冊事件處理程序,在這里沒有。
、事件向下傳遞直到目標(biāo)本身。然后開始冒泡階段,執(zhí)行dosomething(),這個(gè)是注冊在element2冒泡階段的事件處理程序。
、事件繼續(xù)向上傳遞然后檢查是否有父元素在冒泡階段注冊了事件處理程序。
、事件發(fā)現(xiàn)了element1.然后doSomething2()就被執(zhí)行了。
傳統(tǒng)模式下的兼容性
對于那些支持W3C DOM的瀏覽器來說,傳統(tǒng)的事件注冊
element1.onclick = doSomething2;
就被看做是注冊在冒泡階段的。
事件冒泡的使用
很少有設(shè)計(jì)師意識到事件捕獲或者冒泡。在網(wǎng)頁制作的今天,貌似沒必要讓一個(gè)冒泡事件被一系列的事件處理程序來處理。用戶也會在單擊之后發(fā)生一系列事件而感到迷惑,通常你也想讓你的事件處理程序的代碼保持一定的獨(dú)立性。當(dāng)用戶點(diǎn)擊一個(gè)元素,發(fā)生了一些事情,當(dāng)他單擊其他元素,那么其他再發(fā)生其他事情。
當(dāng)然在將來也許會改變,最好讓模式向前兼容。但是如今最實(shí)用的事件捕獲和冒泡就是默認(rèn)函數(shù)的注冊。
它總是會發(fā)生
首先你需要理解的就是事件捕獲或者冒泡總是在發(fā)生的。如果你為你的整個(gè)頁面定義了一個(gè)onclick事件:
復(fù)制代碼 代碼如下:
document.onclick = doSomething;
if (document.captureEvents) document.captureEvents(Event.CLICK);
你在任意元素上的click時(shí)間都會冒泡到頁面然后出發(fā)了這個(gè)事件處理程序。只有當(dāng)前面的事件處理程序明確的阻止冒泡,才不會傳遞到整個(gè)頁面。
使用
因?yàn)槊總€(gè)事件都會在整個(gè)文檔上停止,默認(rèn)的事件處理程序就變得可能。假設(shè)你有一個(gè)這樣的頁面:
復(fù)制代碼 代碼如下:
------------------------------------
| document |
| --------------- ------------ |
| | element1 | | element2 | |
| --------------- ------------ |
------------------------------------
element1.onclick = doSomething;
element2.onclick = doSomething;
document.onclick = defaultFunction;
現(xiàn)在如果用戶點(diǎn)擊了element1或者element2那么doSomething()就會執(zhí)行。在這如果你愿意也可以阻止他的傳播。如果不的話,那么defaultFunction()就會執(zhí)行。用戶在其他地方的點(diǎn)擊也會讓defaultFunction()執(zhí)行。有時(shí)候這可能有用。
設(shè)置全局的事件處理程序在寫拖動(dòng)代碼的時(shí)候就很有必要。通常一個(gè)層上的mousedow事件會選擇這個(gè)層然后對mousemove事件做出回應(yīng)。雖然mousedown通常注冊在這個(gè)層上來避免一些瀏覽器的bug,但是其他的事件處理程序都必須是全局的(document-wide)。
記住瀏覽器邏輯的第一定律:什么都會發(fā)生的,而且經(jīng)常是在你做的準(zhǔn)備最少的時(shí)候??赡馨l(fā)生的是用戶的鼠標(biāo)瘋狂的移動(dòng)然后代碼沒有跟上導(dǎo)致鼠標(biāo)已經(jīng)不再這個(gè)層上了。
如果在某個(gè)層上注冊了onmousemove事件處理程序,如果這個(gè)層不再響應(yīng)鼠標(biāo)的移動(dòng)了,那么肯定會讓用戶感到迷惑。
如果某個(gè)曾上注冊了onmouseup事件處理程序,那么程序會在用戶松開鼠標(biāo)的時(shí)候程序沒有捕捉到造成這個(gè)層還在隨著鼠標(biāo)移動(dòng)。
在這種情況下事件冒泡就很重要,因?yàn)槿值氖录幚沓绦驎WC執(zhí)行的。
關(guān)掉它
但是通常你想關(guān)閉所有的相關(guān)的捕獲和冒泡。另外,如果你的文檔結(jié)構(gòu)非常的復(fù)雜(比如一大堆復(fù)雜的表格之類)你也需要關(guān)閉冒泡來節(jié)省系統(tǒng)資源。要不然瀏覽器就得一個(gè)個(gè)的查看父元素是否有事件處理程序。雖然可能一個(gè)都沒有,但是查找一樣浪費(fèi)時(shí)間。
在微軟模式里你必須講事件的cancelBubble屬性設(shè)置為true。
window.event.cancelBubble = true
在W3C模式中你必須調(diào)用stopPropagation()方法。
e.stopPropagation()
這會阻止這個(gè)事件的冒泡階段。阻止事件的捕獲極端基本上是不可能的。我也想知道為啥。
一個(gè)完整的跨瀏覽器的代碼如下:
復(fù)制代碼 代碼如下:
function doSomething(e)
{
if (!e) var e = window.event;
e.cancelBubble = true;
if (e.stopPropagation) e.stopPropagation();
}
在不支持cancelBubble的瀏覽器里面設(shè)置也不會有啥問題。瀏覽器會創(chuàng)建一個(gè)這樣的屬性。當(dāng)然是沒啥用的,只是為了安全。
currentTarget
我們之前講過,一個(gè)事件包含target或者srcElement包含一個(gè)發(fā)生事件的元素的引用。在我們的例子是element2,因?yàn)橛脩酎c(diǎn)擊了他。
理解在捕獲和冒泡過程中這個(gè)target是不會改變的非常重要:他一直指向element2.
但是假設(shè)我們注冊了下面的事件處理程序:
element1.onclick = doSomething;
element2.onclick = doSomething;
如果用戶點(diǎn)擊element2那么doSomething()執(zhí)行了兩次。那么你怎樣知道那個(gè)HTML元素處理著這個(gè)事件呢?target/scrElement也不能給出答案,從事件一開始就一直指向element2.
為了解決這個(gè)問題W3C添加了currentTarget屬性。它包含一個(gè)正在處理的事件的HTML元素的引用:就是我們想要的那個(gè)。不幸的是微軟模式?jīng)]有類似的屬性。
你也可以用this關(guān)鍵字。在這個(gè)例子里就是指向正在處理的事件的HTML元素,就先currentTarget。
微軟模式的問題
但是當(dāng)你使用微軟的事件注冊模型,this關(guān)鍵字不是只想HTML元素的。然后又沒有一個(gè)像currentTarget類似的屬性,這就意味著如果你這么做:
element1.attachEvent('onclick',doSomething)
element2.attachEvent('onclick',doSomething)
你就不知道那個(gè)HTML元素正在處理事件。這是微軟的事件注冊模式最嚴(yán)重的問題所以就根本不要用他,即使是那些只在IE/win下的程序。
我希望微軟能夠盡快添加一個(gè)類似currentTarget的屬性或者遵循標(biāo)準(zhǔn)?我們設(shè)計(jì)急需啊。
繼續(xù)
如果你想繼續(xù)學(xué)習(xí),請看下一章。
原文地址:http://www.quirksmode.org/js/events_order.html
我的twitter: @rehawk
相關(guān)文章
一文詳解JavaScript中的URL和URLSearchParams
URL,稱為統(tǒng)一資源定位器,指互聯(lián)網(wǎng)上能找到資源定位的字符串,而URLSearchParams對象是專門用于處理url網(wǎng)址信息中的查詢字符串,本文就來帶大家深入了解一下二者的使用2023-05-05用js實(shí)現(xiàn)層隨著內(nèi)容大小動(dòng)態(tài)漸變改變 推薦
以前做谷歌的小工具時(shí),api里提供了一個(gè)很有用的函數(shù),那就是在程序運(yùn)行時(shí)可以使層動(dòng)態(tài)隨內(nèi)容大小而變化,而且是平滑變換,在一些jquery的lightbox里也普遍有這種效果,看起來很酷的樣子。2009-12-12js中調(diào)用微信的掃描二維碼功能的實(shí)現(xiàn)代碼
這篇文章主要介紹了在js中調(diào)用微信的掃描二維碼功能的實(shí)現(xiàn)代碼,本文給大家分享了注意事項(xiàng)及常見問題分析,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-04-04

JavaScript數(shù)據(jù)類型及相互間的轉(zhuǎn)換規(guī)則
這篇文章主要介紹了JavaScript數(shù)據(jù)類型及相互間的轉(zhuǎn)換規(guī)則,文章通過圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下
2022-09-09