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