摘要:事件捕獲團(tuán)隊(duì)提出的另一種事件流叫做事件捕獲。所有節(jié)點(diǎn)中都包含這兩個(gè)方法,并且它們都接受個(gè)參數(shù)要處理的事件名作為事件處理程序的函數(shù)和一個(gè)布爾值。最后這個(gè)布爾值參數(shù)如果是,表示在捕獲階段調(diào)用事件處理程序如果是,表示在冒泡階段調(diào)用事件處理程序。
JavaScript 程序采用了異步事件驅(qū)動(dòng)編程模型。在這種程序設(shè)計(jì)風(fēng)格下,當(dāng)文檔、瀏覽器、元素或與之相關(guān)的對(duì)象發(fā)生某些有趣的事情時(shí),Web 瀏覽器就會(huì)產(chǎn)生事件(event)。例如,當(dāng) Web 瀏覽器加載完文檔、用戶把鼠標(biāo)指針移到超鏈接上或敲擊鍵盤(pán)時(shí),Web 瀏覽器都會(huì)產(chǎn)生事件。如果 JavaScript 應(yīng)用程序關(guān)注特定類型的事件,那么它可以注冊(cè)當(dāng)這類事件發(fā)生時(shí)要調(diào)用的一個(gè)或多個(gè)函數(shù)。請(qǐng)注意,這種風(fēng)格并不只應(yīng)用于 Web 編程,所有使用圖形用戶界面的應(yīng)用程序都采用了它,它們靜待某些事情發(fā)生(即,它們等待事件發(fā)生),然后它們響應(yīng)。
請(qǐng)注意,事件本身并不是一個(gè)需要定義的技術(shù)名詞。簡(jiǎn)而言之,事件就是 Web 瀏覽器通知應(yīng)用程序發(fā)生了什么事情,這種在傳統(tǒng)軟件工程中被稱為觀察員模式。
事件流當(dāng)瀏覽器發(fā)展到第四代時(shí)(IE4 及 Netscape Communicator 4),瀏覽器開(kāi)發(fā)團(tuán)隊(duì)遇到了一個(gè)很有意思的問(wèn)題:頁(yè)面的哪一部分會(huì)擁有某個(gè)特定的事件?要明白這個(gè)問(wèn)題問(wèn)的是什么,可以想象畫(huà)在一張紙上的一組同心圓。如果你把手指放在圓心上,那么你的手指指向的不是一個(gè)圓,而是紙上的所有圓。兩家公司的瀏覽器開(kāi)發(fā)團(tuán)隊(duì)在看待瀏覽器事件方面還是一致的。如果你單擊了某個(gè)按鈕,他們都認(rèn)為單擊事件不僅僅發(fā)生在按鈕上。換句話說(shuō),在單擊按鈕的同時(shí),你也單擊了按鈕的容器元素,甚至也單擊了整個(gè)頁(yè)面。
事件流描述的是從頁(yè)面中接收事件的順序。但有意思的是,IE 和 Netscape 開(kāi)發(fā)團(tuán)隊(duì)居然提出了差不多是完全相反的事件流的概念。IE 的事件流是事件冒泡流,而 Netscape Communicator 的事件流是事件捕獲流。
事件冒泡IE 的事件流叫做事件冒泡(event bubbling),即事件開(kāi)始時(shí)由最具體的元素(文檔中嵌套層次最深的那個(gè)節(jié)點(diǎn))接收,然后逐級(jí)向上傳播到較為不具體的節(jié)點(diǎn)(文檔)。以下面的HTML頁(yè)面為例:
Event Bubbling Example Click Me
如果你單擊了頁(yè)面中的
document 也就是說(shuō),click 事件首先在 Netscape Communicator 團(tuán)隊(duì)提出的另一種事件流叫做事件捕獲(event capturing)。事件捕獲的思想是不太具體的節(jié)點(diǎn)應(yīng)該更早接收到事件,而最具體的節(jié)點(diǎn)應(yīng)該最后接收到事件。事件捕獲的用意在于在事件到達(dá)預(yù)定目標(biāo)之前捕獲它。如果仍以前面的 HTML 頁(yè)面作為演示事件捕獲的例子,那么單擊 document
在事件捕獲過(guò)程中,document 對(duì)象首先接收到 click 事件,然后事件沿 DOM 樹(shù)依次向下,一直傳播到事件的實(shí)際目標(biāo),即 由于老版本的瀏覽器不支持,因此很少有人使用事件捕獲。我們也建議大家放心地使用事件冒泡,在有特殊需要時(shí)再使用事件捕獲。 事件就是用戶或?yàn)g覽器自身執(zhí)行的某種動(dòng)作。諸如 click、load 和 mouseover,都是事件的名字。而響應(yīng)某個(gè)事件的函數(shù)就叫做事件處理程序(或事件偵聽(tīng)器)。事件處理程序的名字以 "on" 開(kāi)頭,因此 click 事件的事件處理程序就是 onclick,load 事件的事件處理程序就是 onload。為事件指定處理程序的方式有好幾種。 某個(gè)元素支持的每種事件,都可以使用一個(gè)與相應(yīng)事件處理程序同名的 HTML 特性來(lái)指定。這個(gè)特性的值應(yīng)該是能夠執(zhí)行的 JavaScript 代碼。例如,要在按鈕被單擊時(shí)執(zhí)行一些 JavaScript,可以像下面這樣編寫(xiě)代碼: 當(dāng)單擊這個(gè)按鈕時(shí),就會(huì)在控制臺(tái)打印 "Clicked"。這個(gè)操作是通過(guò)指定 onclick 特性并將一些 JavaScript 代碼作為它的值來(lái)定義的。由于這個(gè)值是 JavaScript,因此不能在其中使用未經(jīng)轉(zhuǎn)義的 HTML 語(yǔ)法字符,例如和號(hào)(&)、雙引號(hào)("")、小于號(hào)(<)或大于號(hào)(>)。為了避免使用 HTML 實(shí)體,這里使用了單引號(hào)。如果想要使用雙引號(hào),那么就要將代碼改寫(xiě)成如下所示: 在 HTML 中定義的事件處理程序可以包含要執(zhí)行的具體動(dòng)作,也可以調(diào)用在頁(yè)面其他地方定義的腳本,如下面的例子所示: 在這個(gè)例子中,單擊按鈕就會(huì)調(diào)用 showMessage() 函數(shù)。這個(gè)函數(shù)是在一個(gè)獨(dú)立的 元素中定義的,當(dāng)然也可以被包含在一個(gè)外部文件中。事件處理程序中的代碼在執(zhí)行時(shí),有權(quán)訪問(wèn)全局作用域中的任何代碼。 這樣指定事件處理程序具有一些獨(dú)到之處。首先,這樣會(huì)創(chuàng)建一個(gè)封裝著元素屬性值的函數(shù)。這個(gè)函數(shù)中有一個(gè)局部變量 event,也就是事件對(duì)象: 通過(guò) event 變量,可以直接訪問(wèn)事件對(duì)象,你不用自己定義它,也不用從函數(shù)的參數(shù)列表中讀取。 在這個(gè)函數(shù)內(nèi)部,this 值等于事件的目標(biāo)元素,例如: 如此一來(lái),事件處理程序要訪問(wèn)自己的屬性就簡(jiǎn)單多了。下面這行代碼與前面的例子效果相同: 不過(guò),在 HTML 中指定事件處理程序有三個(gè)缺點(diǎn)。首先,存在一個(gè)時(shí)差問(wèn)題。因?yàn)橛脩艨赡軙?huì)在 HTML 元素一出現(xiàn)在頁(yè)面上就觸發(fā)相應(yīng)的事件,但當(dāng)時(shí)的事件處理程序有可能尚不具備執(zhí)行條件。以前面的例子來(lái)說(shuō)明,假設(shè) showMessage() 函數(shù)是在按鈕下方、頁(yè)面的最底部定義的。如果用戶在頁(yè)面解析 showMessage() 函數(shù)之前就單擊了按鈕,就會(huì)引發(fā)錯(cuò)誤。為此,很多HTML事件處理程序都會(huì)被封裝在一個(gè) try-catch 塊中,以便錯(cuò)誤不會(huì)浮出水面,如下面的例子所示: 這樣,如果在 showMessage() 函數(shù)有定義之前單擊了按鈕,用戶將不會(huì)看到 JavaScript 錯(cuò)誤,因?yàn)樵跒g覽器有機(jī)會(huì)處理錯(cuò)誤之前,錯(cuò)誤就被捕獲了。 第二個(gè)缺點(diǎn)是,這樣擴(kuò)展事件處理程序的作用域鏈在不同瀏覽器中會(huì)導(dǎo)致不同結(jié)果。不同 JavaScript 引擎遵循的標(biāo)識(shí)符解析規(guī)則略有差異,很可能會(huì)在訪問(wèn)非限定對(duì)象成員時(shí)出錯(cuò)。 第三個(gè)缺點(diǎn)是,HTML 與 JavaScript 代碼緊密耦合。如果要更換事件處理程序,就要改動(dòng)兩個(gè)地方:HTML 代碼和 JavaScript 代碼。而這正是許多開(kāi)發(fā)人員摒棄 HTML 事件處理程序,轉(zhuǎn)而使用 JavaScript 指定事件處理程序的原因所在。 通過(guò) JavaScript 指定事件處理程序的傳統(tǒng)方式,就是將一個(gè)函數(shù)賦值給一個(gè)事件處理程序?qū)傩浴_@種為事件處理程序賦值的方法是在第四代Web瀏覽器中出現(xiàn)的,而且至今仍然為所有現(xiàn)代瀏覽器所支持。原因一是簡(jiǎn)單,二是具有跨瀏覽器的優(yōu)勢(shì)。要使用 JavaScript 指定事件處理程序,首先必須取得一個(gè)要操作的對(duì)象的引用。 每個(gè)元素(包括 window 和 document)都有自己的事件處理程序?qū)傩裕@些屬性通常全部小寫(xiě),例如 onclick。將這種屬性的值設(shè)置為一個(gè)函數(shù),就可以指定事件處理程序,如下所示: 在此,我們通過(guò)文檔對(duì)象取得了一個(gè)按鈕的引用,然后為它指定了 onclick 事件處理程序。但要注意,在這些代碼運(yùn)行以前不會(huì)指定事件處理程序,因此如果這些代碼在頁(yè)面中位于按鈕后面,就有可能在一段時(shí)間內(nèi)怎么單擊都沒(méi)有反應(yīng)。 使用 DOM1 級(jí)方法指定的事件處理程序被認(rèn)為是元素的方法。因此,這時(shí)候的事件處理程序是在元素的作用域中運(yùn)行;換句話說(shuō),程序中的 this 引用當(dāng)前元素。來(lái)看一個(gè)例子。 單擊按鈕顯示的是元素的 ID,這個(gè) ID 是通過(guò) this.id 取得的。不僅僅是 ID,實(shí)際上可以在事件處理程序中通過(guò) this 訪問(wèn)元素的任何屬性和方法。以這種方式添加的事件處理程序會(huì)在事件流的冒泡階段被處理。 也可以刪除通過(guò) DOM1 級(jí)方法指定的事件處理程序,只要像下面這樣將事件處理程序?qū)傩缘闹翟O(shè)置為 null 即可: 將事件處理程序設(shè)置為 null 之后,再單擊按鈕將不會(huì)有任何動(dòng)作發(fā)生。 DOM2 級(jí)事件定義了兩個(gè)方法,用于處理指定和刪除事件處理程序的操作:addEventListener() 和 removeEventListener()。所有 DOM 節(jié)點(diǎn)中都包含這兩個(gè)方法,并且它們都接受3個(gè)參數(shù):要處理的事件名、作為事件處理程序的函數(shù)和一個(gè)布爾值。最后這個(gè)布爾值參數(shù)如果是 true,表示在捕獲階段調(diào)用事件處理程序;如果是 false,表示在冒泡階段調(diào)用事件處理程序。 要在按鈕上為 click 事件添加事件處理程序,可以使用下列代碼: 上面的代碼為一個(gè)按鈕添加了 onclick 事件處理程序,而且該事件會(huì)在冒泡階段被觸發(fā)(因?yàn)樽詈笠粋€(gè)參數(shù)是 false)。與 DOM1 級(jí)方法一樣,這里添加的事件處理程序也是在其依附的元素的作用域中運(yùn)行。使用 DOM2 級(jí)方法添加事件處理程序的主要好處是可以添加多個(gè)事件處理程序。來(lái)看下面的例子。 這里為按鈕添加了兩個(gè)事件處理程序。這兩個(gè)事件處理程序會(huì)按照添加它們的順序觸發(fā),因此首先會(huì)顯示元素的 ID,其次會(huì)顯示 "Hello world!" 消息。 通過(guò) addEventListener() 添加的事件處理程序只能使用 removeEventListener() 來(lái)移除;移除時(shí)傳入的參數(shù)與添加處理程序時(shí)使用的參數(shù)相同。這也意味著通過(guò) addEventListener() 添加的匿名函數(shù)將無(wú)法移除,如下面的例子所示。 在這個(gè)例子中,我們使用 addEventListener() 添加了一個(gè)事件處理程序。雖然調(diào)用 removeEventListener() 時(shí)看似使用了相同的參數(shù),但實(shí)際上,第二個(gè)參數(shù)與傳入 addEventListener() 中的那一個(gè)是完全不同的函數(shù)。而傳入 removeEventListener() 中的事件處理程序函數(shù)必須與傳 入addEventListener() 中的相同,如下面的例子所示。 重寫(xiě)后的這個(gè)例子沒(méi)有問(wèn)題,是因?yàn)樵?addEventListener() 和 removeEventListener() 中使用了相同的函數(shù)。 大多數(shù)情況下,都是將事件處理程序添加到事件流的冒泡階段,這樣可以最大限度地兼容各種瀏覽器。最好只在需要在事件到達(dá)目標(biāo)之前截獲它的時(shí)候?qū)⑹录幚沓绦蛱砑拥讲东@階段。如果不是特別需要,我們不建議在事件捕獲階段注冊(cè)事件處理程序。 IE9、Firefox、Safari、Chrome 和 Opera 支持 DOM2 級(jí)事件處理程序。 IE 實(shí)現(xiàn)了與 DOM 中類似的兩個(gè)方法:attachEvent() 和 detachEvent()。這兩個(gè)方法接受相同的兩個(gè)參數(shù):事件處理程序名稱與事件處理程序函數(shù)。由于 IE8 及更早版本只支持事件冒泡,所以通過(guò) attachEvent() 添加的事件處理程序都會(huì)被添加到冒泡階段。 要使用 attachEvent() 為按鈕添加一個(gè)事件處理程序,可以使用以下代碼。 注意,attachEvent() 的第一個(gè)參數(shù)是 "onclick",而非 DOM 的 addEventListener() 方法中的 "click"。 在 IE 中使用 attachEvent() 與使用 DOM1 級(jí)方法的主要區(qū)別在于事件處理程序的作用域。在使用 DOM1 級(jí)方法的情況下,事件處理程序會(huì)在其所屬元素的作用域內(nèi)運(yùn)行;在使用 attachEvent() 方法的情況下,事件處理程序會(huì)在全局作用域中運(yùn)行,因此 this 等于 window。來(lái)看下面的例子。 在編寫(xiě)跨瀏覽器的代碼時(shí),牢記這一區(qū)別非常重要。 與 addEventListener() 類似,attachEvent() 方法也可以用來(lái)為一個(gè)元素添加多個(gè)事件處理程序。不過(guò),與 DOM 方法不同的是,這些事件處理程序不是以添加它們的順序執(zhí)行,而是以相反的順序被觸發(fā)。 使用 attachEvent() 添加的事件可以通過(guò) detachEvent() 來(lái)移除,條件是必須提供相同的參數(shù)。與 DOM 方法一樣,這也意味著添加的匿名函數(shù)將不能被移除。不過(guò),只要能夠?qū)?duì)相同函數(shù)的引用傳給 detachEvent(),就可以移除相應(yīng)的事件處理程序。 支持 IE 事件處理程序的瀏覽器有 IE 和 Opera。 為了以跨瀏覽器的方式處理事件,不少開(kāi)發(fā)人員會(huì)使用能夠隔離瀏覽器差異的 JavaScript 庫(kù),還有一些開(kāi)發(fā)人員會(huì)自己開(kāi)發(fā)最合適的事件處理的方法。自己編寫(xiě)代碼其實(shí)也不難,只要恰當(dāng)?shù)厥褂媚芰z測(cè)即可。要保證處理事件的代碼能在大多數(shù)瀏覽器下一致地運(yùn)行,只需關(guān)注冒泡階段。 第一個(gè)要?jiǎng)?chuàng)建的方法是 addHandler(),它的職責(zé)是視情況分別使用 DOM1 級(jí)方法、DOM2 級(jí)方法或 IE 方法來(lái)添加事件。這個(gè)方法屬于一個(gè)名叫 EventUtil 的對(duì)象,本書(shū)將使用這個(gè)對(duì)象來(lái)處理瀏覽器間的差異。addHandler() 方法接受3個(gè)參數(shù):要操作的元素、事件名稱和事件處理程序函數(shù)。 與 addHandler() 對(duì)應(yīng)的方法是 removeHandler(),它也接受相同的參數(shù)。這個(gè)方法的職責(zé)是移除之前添加的事件處理程序——無(wú)論該事件處理程序是采取什么方式添加到元素中的,如果其他方法無(wú)效,默認(rèn)采用 DOM1 級(jí)方法。 EventUtil 的用法如下所示。 這兩個(gè)方法首先都會(huì)檢測(cè)傳入的元素中是否存在 DOM2 級(jí)方法。如果存在 DOM2 級(jí)方法,則使用該方法:傳入事件類型、事件處理程序函數(shù)和第三個(gè)參數(shù) false(表示冒泡階段)。如果存在的是 IE 的方法,則采取第二種方案。注意,為了在 IE8 及更早版本中運(yùn)行,此時(shí)的事件類型必須加上 "on" 前綴。最后一種可能就是使用 DOM1 級(jí)方法(在現(xiàn)代瀏覽器中,應(yīng)該不會(huì)執(zhí)行這里的代碼)。此時(shí),我們使用的是方括號(hào)語(yǔ)法來(lái)將屬性名指定為事件處理程序,或者將屬性設(shè)置為 null。 可以像下面這樣使用 EventUtil 對(duì)象: addHandler() 和 removeHandler() 沒(méi)有考慮到所有的瀏覽器問(wèn)題,例如在 IE 中的作用域問(wèn)題。不過(guò),使用它們添加和移除事件處理程序還是足夠了。 在觸發(fā) DOM 上的某個(gè)事件時(shí),會(huì)產(chǎn)生一個(gè)事件對(duì)象 event,這個(gè)對(duì)象中包含著所有與事件有關(guān)的信息。包括導(dǎo)致事件的元素、事件的類型以及其他與特定事件相關(guān)的信息。例如,鼠標(biāo)操作導(dǎo)致的事件對(duì)象中,會(huì)包含鼠標(biāo)位置的信息,而鍵盤(pán)操作導(dǎo)致的事件對(duì)象中,會(huì)包含與按下的鍵有關(guān)的信息。所有瀏覽器都支持 event 對(duì)象,但支持方式不同。 兼容 DOM 的瀏覽器會(huì)將一個(gè) event 對(duì)象傳入到事件處理程序中。無(wú)論指定事件處理程序時(shí)使用什么方法(DOM1 級(jí)或 DOM2 級(jí)),都會(huì)傳入 event 對(duì)象。來(lái)看下面的例子。 這個(gè)例子中的兩個(gè)事件處理程序都會(huì)彈出一個(gè)警告框,顯示由 event.type 屬性表示的事件類型。這個(gè)屬性始終都會(huì)包含被觸發(fā)的事件類型,例如 "click"(與傳入 addEventListener() 和 removeEventListener() 中的事件類型一致)。 在通過(guò) HTML 特性指定事件處理程序時(shí),變量 event 中保存著 event 對(duì)象。請(qǐng)看下面的例子。 以這種方式提供 event 對(duì)象,可以讓 HTML 特性事件處理程序與 JavaScript 函數(shù)執(zhí)行相同的操作。 event 對(duì)象包含與創(chuàng)建它的特定事件有關(guān)的屬性和方法。觸發(fā)的事件類型不一樣,可用的屬性和方法也不一樣。不過(guò),所有事件都會(huì)有下表列出的成員。 bubbles,表明事件是否冒泡。 cancelable,表明是否可以取消事件的默認(rèn)行為。 currentTarget,其事件處理程序當(dāng)前正在處理事件的那個(gè)元素。 defaultPrevented,為 true 表示已經(jīng)調(diào)用了 preventDefault()(DOM3 級(jí)事件中新增)。 detail,與事件相關(guān)的細(xì)節(jié)信息。 eventPhase,調(diào)用事件處理程序的階段:1表示捕獲階段,2表示“處于目標(biāo)”,3表示冒泡階段。 preventDefault(),取消事件的默認(rèn)行為。如果 cancelable 是 true,則可以使用這個(gè)方法。 stopImmediatePropagation(),取消事件的進(jìn)一步捕獲或冒泡,同時(shí)阻止任何事件處理程序被調(diào)用(DOM3 級(jí)事件中新增)。 stopPropagation(),取消事件的進(jìn)一步捕獲或冒泡。如果 bubbles 為 true,則可以使用這個(gè)方法。 target,事件的目標(biāo)。 trusted,為 true 表示事件是瀏覽器生成的。為 false 表示事件是由開(kāi)發(fā)人員通過(guò) JavaScript 創(chuàng)建的(DOM3 級(jí)事件中新增)。 type,被觸發(fā)的事件的類型。 view,與事件關(guān)聯(lián)的抽象視圖,等同于發(fā)生事件的 window 對(duì)象。 在事件處理程序內(nèi)部,對(duì)象 this 始終等于 currentTarget 的值,而 target 則只包含事件的實(shí)際目標(biāo)。如果直接將事件處理程序指定給了目標(biāo)元素,則 this、currentTarget 和 target 包含相同的值。來(lái)看下面的例子。 這個(gè)例子檢測(cè)了 currentTarget 和 target 與 this 的值。由于 click 事件的目標(biāo)是按鈕,因此這三個(gè)值是相等的。如果事件處理程序存在于按鈕的父節(jié)點(diǎn)中(例如 document.body),那么這些值是不相同的。再看下面的例子。 當(dāng)單擊這個(gè)例子中的按鈕時(shí),this 和 currentTarget 都等于document.body,因?yàn)槭录幚沓绦蚴亲?cè)到這個(gè)元素上的。然而,target 元素卻等于按鈕元素,因?yàn)樗?click 事件真正的目標(biāo)。由于按鈕上并沒(méi)有注冊(cè)事件處理程序,結(jié)果 click 事件就冒泡到了 document.body,在那里事件才得到了處理。 在需要通過(guò)一個(gè)函數(shù)處理多個(gè)事件時(shí),可以使用 type 屬性。例如: 這個(gè)例子定義了一個(gè)名為 handler 的函數(shù),用于處理3種事件:click、mouseover 和 mouseout。當(dāng)單擊按鈕時(shí),會(huì)出現(xiàn)一個(gè)與前面例子中一樣的警告框。當(dāng)按鈕移動(dòng)到按鈕上面時(shí),背景顏色應(yīng)該會(huì)變成紅色,而當(dāng)鼠標(biāo)移動(dòng)出按鈕的范圍時(shí),背景顏色應(yīng)該會(huì)恢復(fù)為默認(rèn)值。這里通過(guò)檢測(cè) event.type 屬性,讓函數(shù)能夠確定發(fā)生了什么事件,并執(zhí)行相應(yīng)的操作。 要阻止特定事件的默認(rèn)行為,可以使用 preventDefault() 方法。例如,鏈接的默認(rèn)行為就是在被單擊時(shí)會(huì)導(dǎo)航到其 href 特性指定的 URL。如果你想阻止鏈接導(dǎo)航這一默認(rèn)行為,那么通過(guò)鏈接的 onclick 事件處理程序可以取消它,如下面的例子所示。 只有 cancelable 屬性設(shè)置為 true 的事件,才可以使用 preventDefault() 來(lái)取消其默認(rèn)行為。 另外,stopPropagation() 方法用于立即停止事件在 DOM 層次中的傳播,即取消進(jìn)一步的事件捕獲或冒泡。例如,直接添加到一個(gè)按鈕的事件處理程序可以調(diào)用 stopPropagation(),從而避免觸發(fā)注冊(cè)在 document.body 上面的事件處理程序,如下面的例子所示。 對(duì)于這個(gè)例子而言,如果不調(diào)用 stopPropagation(),就會(huì)在單擊按鈕時(shí)出現(xiàn)兩個(gè)警告框。可是,由于 click 事件根本不會(huì)傳播到 document.body,因此就不會(huì)觸發(fā)注冊(cè)在這個(gè)元素上的 onclick 事件處理程序。 事件對(duì)象的 eventPhase 屬性,可以用來(lái)確定事件當(dāng)前正位于事件流的哪個(gè)階段。如果是在捕獲階段調(diào)用的事件處理程序,那么 eventPhase 等于 1;如果事件處理程序處于目標(biāo)對(duì)象上,則 eventPhase 等于 2;如果是在冒泡階段調(diào)用的事件處理程序,eventPhase 等于 3。這里要注意的是,盡管“處于目標(biāo)”發(fā)生在冒泡階段,但 eventPhase 仍然一直等于 2。來(lái)看下面的例子。 當(dāng)單擊這個(gè)例子中的按鈕時(shí),首先執(zhí)行的事件處理程序是在捕獲階段觸發(fā)的添加到 document.body 中的那一個(gè),結(jié)果會(huì)彈出一個(gè)警告框顯示表示 eventPhase 的 1。接著,會(huì)觸發(fā)在按鈕上注冊(cè)的事件處理程序,此時(shí)的 eventPhase 值為 2。最后一個(gè)被觸發(fā)的事件處理程序,是在冒泡階段執(zhí)行的添加到 document.body 上的那一個(gè),顯示 eventPhase 的值為 3。而當(dāng) eventPhase 等于 2 時(shí),this、target 和 currentTarget 始終都是相等的。 只有在事件處理程序執(zhí)行期間,event對(duì)象才會(huì)存在;一旦事件處理程序執(zhí)行完成,event對(duì)象就會(huì)被銷毀。 與訪問(wèn) DOM 中的 event 對(duì)象不同,要訪問(wèn)IE中的 event 對(duì)象有幾種不同的方式,取決于指定事件處理程序的方法。在使用 DOM1 級(jí)方法添加事件處理程序時(shí),event 對(duì)象作為 window 對(duì)象的一個(gè)屬性存在。來(lái)看下面的例子。 在此,我們通過(guò) window.event 取得了 event 對(duì)象,并檢測(cè)了被觸發(fā)事件的類型(IE 中的 type 屬性與 DOM 中的 type 屬性是相同的)。可是,如果事件處理程序是使用 attachEvent() 添加的,那么就會(huì)有一個(gè) event 對(duì)象作為參數(shù)被傳入事件處理程序函數(shù)中,如下所示。 在像這樣使用 attachEvent() 的情況下,也可以通過(guò) window 對(duì)象來(lái)訪問(wèn) event 對(duì)象,就像使用 DOM1 級(jí)方法時(shí)一樣。不過(guò)為方便起見(jiàn),同一個(gè)對(duì)象也會(huì)作為參數(shù)傳遞。 如果是通過(guò) HTML 特性指定的事件處理程序,那么還可以通過(guò)一個(gè)名叫 event 的變量來(lái)訪問(wèn) event 對(duì)象(與 DOM 中的事件模型相同)。再看一個(gè)例子。 IE 的 event 對(duì)象同樣也包含與創(chuàng)建它的事件相關(guān)的屬性和方法。其中很多屬性和方法都有對(duì)應(yīng)的或者相關(guān)的 DOM 屬性和方法。與 DOM 的 event 對(duì)象一樣,這些屬性和方法也會(huì)因?yàn)槭录愋偷牟煌煌惺录?duì)象都會(huì)包含下表所列的屬性和方法。 cancelBubble,默認(rèn)值為 false,但將其設(shè)置為 true 就可以取消事件冒泡(與 DOM 中的 stopPropagation() 方法的作用相同)。 returnValue,默認(rèn)值為 true,但將其設(shè)置為 false 就可以取消事件的默認(rèn)行為(與 DOM 中的 preventDefault() 方法的作用相同) 。 srcElement,事件的目標(biāo)(與 DOM 中的 target 屬性相同) 。 type,被觸發(fā)的事件的類型 。 因?yàn)槭录幚沓绦虻淖饔糜蚴歉鶕?jù)指定它的方式來(lái)確定的,所以不能認(rèn)為 this 會(huì)始終等于事件目標(biāo)。故而,最好還是使用 event.srcElement 比較保險(xiǎn)。例如: 在第一個(gè)事件處理程序中(使用 DOM1 級(jí)方法指定的),srcElement 屬性等于 this,但在第二個(gè)事件處理程序中,這兩者的值不相同。 如前所述,returnValue 屬性相當(dāng)于 DOM 中的 preventDefault() 方法,它們的作用都是取消給定事件的默認(rèn)行為。只要將 returnValue 設(shè)置為 false,就可以阻止默認(rèn)行為。來(lái)看下面的例子。 這個(gè)例子在 onclick 事件處理程序中使用 returnValue 達(dá)到了阻止鏈接默認(rèn)行為的目的。與 DOM 不同的是,在此沒(méi)有辦法確定事件是否能被取消。 相應(yīng)地,cancelBubble 屬性與 DOM 中的 stopPropagation() 方法作用相同,都是用來(lái)停止事件冒泡的。由于IE不支持事件捕獲,因而只能取消事件冒泡;但 stopPropagatioin() 可以同時(shí)取消事件捕獲和冒泡。例如: 通過(guò)在 onclick 事件處理程序中將 cancelBubble 設(shè)置為 true,就可阻止事件通過(guò)冒泡而觸發(fā) document.body 中注冊(cè)的事件處理程序。結(jié)果,在單擊按鈕之后,只會(huì)顯示一個(gè)警告框。 雖然 DOM 和 IE 中的 event 對(duì)象不同,但基于它們之間的相似性依舊可以拿出跨瀏覽器的方案來(lái)。IE中 event 對(duì)象的全部信息和方法 DOM 對(duì)象中都有,只不過(guò)實(shí)現(xiàn)方式不一樣。不過(guò),這種對(duì)應(yīng)關(guān)系讓實(shí)現(xiàn)兩種事件模型之間的映射非常容易。可以對(duì)前面介紹的 EventUtil 對(duì)象加以增強(qiáng),添加如下方法以求同存異。 以上代碼顯示,我們?yōu)?EventUtil 添加了4個(gè)新方法。第一個(gè)是 getEvent(),它返回對(duì) event 對(duì)象的引用。考慮到 IE 中事件對(duì)象的位置不同,可以使用這個(gè)方法來(lái)取得 event 對(duì)象,而不必?fù)?dān)心指定事件處理程序的方式。在使用這個(gè)方法時(shí),必須假設(shè)有一個(gè)事件對(duì)象傳入到事件處理程序中,而且要把該變量傳給這個(gè)方法,如下所示。 在兼容 DOM 的瀏覽器中,event 變量只是簡(jiǎn)單地傳入和返回。而在 IE 中,event 參數(shù)是未定義的 undefined,因此就會(huì)返回 window.event。將這一行代碼添加到事件處理程序的開(kāi)頭,就可以確保隨時(shí)都能使用 event 對(duì)象,而不必?fù)?dān)心用戶使用的是什么瀏覽器。 第二個(gè)方法是 getTarget(),它返回事件的目標(biāo)。在這個(gè)方法內(nèi)部,會(huì)檢測(cè) event 對(duì)象的 target 屬性,如果存在則返回該屬性的值;否則,返回 srcElement 屬性的值。可以像下面這樣使用這個(gè)方法。 第三個(gè)方法是 preventDefault(),用于取消事件的默認(rèn)行為。在傳入 event 對(duì)象后,這個(gè)方法會(huì)檢查是否存在 preventDefault() 方法,如果存在則調(diào)用該方法。如果 preventDefault() 方法不存在,則將 returnValue 設(shè)置為 false。下面是使用這個(gè)方法的例子。 以上代碼可以確保在所有瀏覽器中單擊該鏈接都不會(huì)打開(kāi)另一個(gè)頁(yè)面。首先,使用 EventUtil.getEvent() 取得 event 對(duì)象,然后將其傳入到 EventUtil.preventDefault() 以取消默認(rèn)行為。 第四個(gè)方法是 stopPropagation(),其實(shí)現(xiàn)方式類似。首先嘗試使用DOM方法阻止事件流,否則就使用 cancelBubble 屬性。下面看一個(gè)例子。 在此,首先使用 EventUtil.getEvent() 取得了 event 對(duì)象,然后又將其傳入到 EventUtil.stopPropagation()。別忘了由于 IE 不支持事件捕獲,因此這個(gè)方法在跨瀏覽器的情況下,也只能用來(lái)阻止事件冒泡。 Web 瀏覽器中可能發(fā)生的事件有很多類型。如前所述,不同的事件類型具有不同的信息,而 DOM3 級(jí)事件規(guī)定了以下幾類事件。 UI(User Interface,用戶界面)事件,當(dāng)用戶與頁(yè)面上的元素交互時(shí)觸發(fā); 焦點(diǎn)事件,當(dāng)元素獲得或失去焦點(diǎn)時(shí)觸發(fā); 鼠標(biāo)事件,當(dāng)用戶通過(guò)鼠標(biāo)在頁(yè)面上執(zhí)行操作時(shí)觸發(fā); 滾輪事件,當(dāng)使用鼠標(biāo)滾輪(或類似設(shè)備)時(shí)觸發(fā); 文本事件,當(dāng)在文檔中輸入文本時(shí)觸發(fā); 鍵盤(pán)事件,當(dāng)用戶通過(guò)鍵盤(pán)在頁(yè)面上執(zhí)行操作時(shí)觸發(fā); 合成事件,當(dāng)為IME(Input Method Editor,輸入法編輯器)輸入字符時(shí)觸發(fā); 變動(dòng)(mutation)事件,當(dāng)?shù)讓?DOM 結(jié)構(gòu)發(fā)生變化時(shí)觸發(fā)。 變動(dòng)名稱事件,當(dāng)元素或?qū)傩悦儎?dòng)時(shí)觸發(fā)。此類事件已經(jīng)被廢棄,沒(méi)有任何瀏覽器實(shí)現(xiàn)它們,因此本章不做介紹。 除了這幾類事件之外,HTML5 也定義了一組事件,而有些瀏覽器還會(huì)在 DOM 和 BOM 中實(shí)現(xiàn)其他專有事件。這些專有的事件一般都是根據(jù)開(kāi)發(fā)人員需求定制的,沒(méi)有什么規(guī)范,因此不同瀏覽器的實(shí)現(xiàn)有可能不一致。 DOM3 級(jí)事件模塊在 DOM2 級(jí)事件模塊基礎(chǔ)上重新定義了這些事件,也添加了一些新事件。包括 IE9 在內(nèi)的所有主流瀏覽器都支持 DOM2 級(jí)事件。 IE9 也支持 DOM3 級(jí)事件。 想要了解更多 DOM 和 HTML5 事件,請(qǐng)參見(jiàn)最新版的 W3C 規(guī)范: 事件是將 JavaScript 與網(wǎng)頁(yè)聯(lián)系在一起的主要方式。DOM3 級(jí)事件規(guī)范和 HTML5 定義了常見(jiàn)的大多數(shù)事件。即使有規(guī)范定義了基本事件,但很多瀏覽器仍然在規(guī)范之外實(shí)現(xiàn)了自己的專有事件,從而為開(kāi)發(fā)人員提供更多掌握用戶交互的手段。有些專有事件與特定設(shè)備關(guān)聯(lián),例如移動(dòng) Safari 中的 orientationchange 事件就是特定關(guān)聯(lián) iOS 設(shè)備的。 在使用事件時(shí),需要考慮如下一些內(nèi)存與性能方面的問(wèn)題。 有必要限制一個(gè)頁(yè)面中事件處理程序的數(shù)量,數(shù)量太多會(huì)導(dǎo)致占用大量?jī)?nèi)存,而且也會(huì)讓用戶感覺(jué)頁(yè)面反應(yīng)不夠靈敏。 建立在事件冒泡機(jī)制之上的事件委托技術(shù),可以有效地減少事件處理程序的數(shù)量。 建議在瀏覽器卸載頁(yè)面之前移除頁(yè)面中的所有事件處理程序。 可以使用 JavaScript 在瀏覽器中模擬事件。DOM2 級(jí)事件和 DOM3 級(jí)事件規(guī)范規(guī)定了模擬事件的方法,為模擬各種有定義的事件提供了方便。此外,通過(guò)組合使用一些技術(shù),還可以在某種程度上模擬鍵盤(pán)事件。IE8 及之前版本同樣支持事件模擬,只不過(guò)模擬的過(guò)程有些差異。 憑理解和記憶手寫(xiě) EventUtil 通用類。 關(guān)注微信公眾號(hào)「劼哥舍」回復(fù)「答案」,獲取關(guān)卡詳解。 文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。 轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/91153.html 摘要:對(duì)象數(shù)組初始化表達(dá)式,闖關(guān)記之上文檔對(duì)象模型是針對(duì)和文檔的一個(gè)。闖關(guān)記之?dāng)?shù)組數(shù)組是值的有序集合。數(shù)組是動(dòng)態(tài)的,根闖關(guān)記之語(yǔ)法的語(yǔ)法大量借鑒了及其他類語(yǔ)言如和的語(yǔ)法。
《JavaScript 闖關(guān)記》之 DOM(下)
Element 類型 除了 Document 類型之外,Element 類型就要算是 Web 編程中最常用的類型了。Element 類型用于表現(xiàn) XML 或 HTML 元素... 摘要:瀏覽器只是實(shí)現(xiàn)的宿主環(huán)境之一,其他宿主環(huán)境包括和。年月,版發(fā)布,成為國(guó)際標(biāo)準(zhǔn)。事件定義了事件和事件處理的接口。對(duì)于已經(jīng)正式納入標(biāo)準(zhǔn)的來(lái)說(shuō),盡管各瀏覽器都實(shí)現(xiàn)了某些眾所周知的共同特性,但其他特性還是會(huì)因?yàn)g覽器而異。
JavaScript 是面向 Web 的編程語(yǔ)言,絕大多數(shù)現(xiàn)代網(wǎng)站都使用了 JavaScript,并且所有的現(xiàn)代 Web 瀏覽器(電腦,手機(jī),平板)均包含了 JavaScri... 摘要:的語(yǔ)法大量借鑒了及其他類語(yǔ)言如和的語(yǔ)法。也就是說(shuō),關(guān)鍵字變量函數(shù)名和所有的標(biāo)識(shí)符都必須采取一致的大小寫(xiě)形式。中的字面量有字符串?dāng)?shù)字布爾值對(duì)象數(shù)組函數(shù)正則表達(dá)式,以及特殊的值。這是為了不破壞語(yǔ)法而特意選定的語(yǔ)法。
JavaScript 的語(yǔ)法大量借鑒了 C 及其他類 C 語(yǔ)言(如 Java 和 Perl)的語(yǔ)法。因此,熟悉這些語(yǔ)言的開(kāi)發(fā)人員在接受 JavaScript 更加寬松的語(yǔ)法時(shí),... 摘要:使用元素嵌入代碼時(shí),只需為指定屬性。需要注意的是,帶有屬性的元素不應(yīng)該在其和元素之間再包含額外的代碼。在包含外部文件時(shí),必須將屬性設(shè)置為指向相應(yīng)文件的。所有元素都會(huì)按照他們?cè)陧?yè)面中出現(xiàn)的先后順序依次被解析。關(guān)注,獲取最新動(dòng)態(tài)。
當(dāng)學(xué)習(xí)一門(mén)新的編程語(yǔ)言的時(shí)候,應(yīng)該邊學(xué)邊做,反復(fù)演練以加深理解。因此,你需要一個(gè) JavaScript 解釋器。幸運(yùn)的是,每一個(gè) Web 瀏覽器都包含一個(gè) Ja... 摘要:本課程之所以叫做闖關(guān)記,是因?yàn)椴糠终鹿?jié)精心設(shè)計(jì)了挑戰(zhàn)關(guān)卡,通過(guò)提供更多的實(shí)戰(zhàn)機(jī)會(huì),讓大家可以循序漸進(jìn)地有目的地有挑戰(zhàn)地開(kāi)展學(xué)習(xí)。課程結(jié)構(gòu)及目錄以下目錄只是初步構(gòu)想,課程結(jié)構(gòu)及內(nèi)容會(huì)根據(jù)實(shí)際情況隨時(shí)進(jìn)行調(diào)整。
為何寫(xiě)作此課程
stone 主要負(fù)責(zé)基于 Web 的企業(yè)內(nèi)部管理系統(tǒng)的開(kāi)發(fā),雖然能夠熟練地使用 JavaScript,但隨著對(duì) JavaScript 的理解越來(lái)越深,才發(fā)現(xiàn)自己尚... 摘要:把上面的函數(shù)聲明改為等價(jià)的函數(shù)表達(dá)式,就會(huì)在執(zhí)行期間導(dǎo)致錯(cuò)誤。換句話說(shuō),引用的是函數(shù)據(jù)以執(zhí)行的環(huán)境對(duì)象當(dāng)在網(wǎng)頁(yè)的全局作用域中調(diào)用函數(shù)時(shí),對(duì)象引用的就是。這兩個(gè)方法的用途都是在特定的作用域中調(diào)用函數(shù),實(shí)際上等于設(shè)置函數(shù)體內(nèi)對(duì)象的值。
函數(shù)是一段代碼,它只定義一次,但可以被執(zhí)行或調(diào)用任意次。在 JavaScript 里,函數(shù)即對(duì)象,程序可以隨意操控它們。比如,可以把函數(shù)賦值給變量,或者作為... 閱讀 3732·2023-04-25 17:45 閱讀 3439·2021-09-04 16:40 閱讀 1008·2019-08-30 13:54 閱讀 2140·2019-08-29 12:59 閱讀 1409·2019-08-26 12:11 閱讀 3285·2019-08-23 15:17 閱讀 1528·2019-08-23 12:07 閱讀 3890·2019-08-22 18:00
var btn = document.getElementById("myBtn");
btn.onclick = function(){
console.log("Clicked");
};
var btn = document.getElementById("myBtn");
btn.onclick = function(){
console.log(this.id); // "myBtn"
};
btn.onclick = null; // 刪除事件處理程序
var btn = document.getElementById("myBtn");
btn.addEventListener("click", function(){
console.log(this.id);
}, false);
var btn = document.getElementById("myBtn");
btn.addEventListener("click", function(){
console.log(this.id);
}, false);
btn.addEventListener("click", function(){
console.log("Hello world!");
}, false);
var btn = document.getElementById("myBtn");
btn.addEventListener("click", function(){
console.log(this.id);
}, false);
btn.removeEventListener("click", function(){ // 沒(méi)有用!
console.log(this.id);
}, false);
var btn = document.getElementById("myBtn");
var handler = function(){
console.log(this.id);
};
btn.addEventListener("click", handler, false);
btn.removeEventListener("click", handler, false); // 有效!
IE 事件處理程序
var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function(){
console.log("Clicked");
});
var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function(){
console.log(this === window); // true
});
跨瀏覽器的事件處理程序
var EventUtil = {
addHandler: function(element, type, handler){
if (element.addEventListener){
element.addEventListener(type, handler, false);
} else if (element.attachEvent){
element.attachEvent("on" + type, handler);
} else {
element["on" + type] = handler;
}
},
removeHandler: function(element, type, handler){
if (element.removeEventListener){
element.removeEventListener(type, handler, false);
} else if (element.detachEvent){
element.detachEvent("on" + type, handler);
} else {
element["on" + type] = null;
}
}
};
var btn = document.getElementById("myBtn");
var handler = function(){
console.log("Clicked");
};
EventUtil.addHandler(btn, "click", handler);
EventUtil.removeHandler(btn, "click", handler);
var btn = document.getElementById("myBtn");
btn.onclick = function(event){
console.log(event.type); // "click"
};
btn.addEventListener("click", function(event){
console.log(event.type); // "click"
}, false);
var btn = document.getElementById("myBtn");
btn.onclick = function(event){
console.log(event.currentTarget === this); // true
console.log(event.target === this); // true
};
document.body.onclick = function(event){
console.log(event.currentTarget === document.body); // true
console.log(this === document.body); // true
console.log(event.target === document.getElementById("myBtn")); // true
};
var btn = document.getElementById("myBtn");
var handler = function(event){
switch(event.type){
case "click":
console.log("Clicked");
break;
case "mouseover":
event.target.style.backgroundColor = "red";
break;
case "mouseout":
event.target.style.backgroundColor = "";
break;
}
};
btn.onclick = handler;
btn.onmouseover = handler;
btn.onmouseout = handler;
var link = document.getElementById("myLink");
link.onclick = function(event){
event.preventDefault();
};
var btn = document.getElementById("myBtn");
btn.onclick = function(event){
console.log("Clicked");
event.stopPropagation();
};
document.body.onclick = function(event){
console.log("Body clicked");
};
var btn = document.getElementById("myBtn");
btn.onclick = function(event){
console.log(event.eventPhase); // 2
};
document.body.addEventListener("click", function(event){
console.log(event.eventPhase); // 1
}, true);
document.body.onclick = function(event){
console.log(event.eventPhase); // 3
};
IE 中的事件對(duì)象
var btn = document.getElementById("myBtn");
btn.onclick = function(){
var event = window.event;
console.log(event.type); // "click"
};
var btn = document.getElementById("myBtn");
btn.attachEvent("onclick", function(event){
console.log(event.type); // "click"
});
var btn = document.getElementById("myBtn");
btn.onclick = function(){
console.log(window.event.srcElement === this); // true
};
btn.attachEvent("onclick", function(event){
console.log(event.srcElement === this); // false
});
var link = document.getElementById("myLink");
link.onclick = function(){
window.event.returnValue = false;
};
var btn = document.getElementById("myBtn");
btn.onclick = function(){
console.log("Clicked");
window.event.cancelBubble = true;
};
document.body.onclick = function(){
console.log("Body clicked");
};
var EventUtil = {
addHandler: function(element, type, handler){
// 省略的代碼
},
getEvent: function(event){
return event ? event : window.event;
},
getTarget: function(event){
return event.target || event.srcElement;
},
preventDefault: function(event){
if (event.preventDefault){
event.preventDefault();
} else {
event.returnValue = false;
}
},
removeHandler: function(element, type, handler){
// 省略的代碼
},
stopPropagation: function(event){
if (event.stopPropagation){
event.stopPropagation();
} else {
event.cancelBubble = true;
}
}
};
btn.onclick = function(event){
event = EventUtil.getEvent(event);
};
btn.onclick = function(event){
event = EventUtil.getEvent(event);
var target = EventUtil.getTarget(event);
};
var link = document.getElementById("myLink");
link.onclick = function(event){
event = EventUtil.getEvent(event);
EventUtil.preventDefault(event);
};
var btn = document.getElementById("myBtn");
btn.onclick = function(event){
console.log("Clicked");
event = EventUtil.getEvent(event);
EventUtil.stopPropagation(event);
};
document.body.onclick = function(event){
console.log("Body clicked");
};
小結(jié)
https://www.w3.org/TR/uievents/var EventUtil = {
addHandler: function(element, type, handler){
// 待補(bǔ)充的代碼
},
removeHandler: function(element, type, handler){
// 待補(bǔ)充的代碼
},
getEvent: function(event){
// 待補(bǔ)充的代碼
},
getTarget: function(event){
// 待補(bǔ)充的代碼
},
preventDefault: function(event){
// 待補(bǔ)充的代碼
},
stopPropagation: function(event){
// 待補(bǔ)充的代碼
}
};
更多
關(guān)注 https://github.com/stone0090/javascript-lessons,獲取最新動(dòng)態(tài)。相關(guān)文章
JavaScript 闖關(guān)記
《JavaScript 闖關(guān)記》之簡(jiǎn)介
《JavaScript 闖關(guān)記》之語(yǔ)法
《JavaScript 闖關(guān)記》之初探
《JavaScript 闖關(guān)記》
《JavaScript 闖關(guān)記》之函數(shù)
發(fā)表評(píng)論
0條評(píng)論
ConardLi
男|高級(jí)講師
TA的文章
閱讀更多
python3.6安裝tensorflow
為什么選擇了互聯(lián)網(wǎng)行業(yè)?都說(shuō)35歲后的程序員會(huì)失業(yè),是真的么?那我搞測(cè)試沖到50歲...
小程序爬坑記
CSS入門(mén)指南-2:盒子模型、浮動(dòng)和清除
Node.js 事件循環(huán)工作流程 & 生命周期
可構(gòu)造樣式表 - 通過(guò)javascript來(lái)生成css的新方式
細(xì)數(shù) JavaScript 實(shí)用黑科技(一)
如何判斷用戶是否已關(guān)注公眾號(hào)