摘要:事件相關內容當用戶與瀏覽器發生的一些交互時如果希望去獲得用戶行為就需要借助事件來完成事件部分內容在中重要性不言而喻羅列需要了解與事件相關的知識如下這也是面試中遇到的問題事件的級別事件模型事件流事件處理程序描述事件捕獲冒泡的具體流程對象常見的
DOM事件相關內容
當用戶與瀏覽器發生的一些交互時, 如果希望去獲得用戶行為, 就需要借助事件來完成. 事件部分內容在 JS中重要性不言而喻.
羅列需要了解與事件相關的知識如下: 這也是面試中遇到的問題.
DOM 事件的級別
DOM 事件模型
DOM 事件流
DOM 事件處理程序
描述DOM事件捕獲(冒泡)的具體流程
Event對象常見的應用
自定義事件
事件級別DOM0
DOM2
DOM3
DOM 事件模型事件冒泡
事件捕獲
什么是事件流要想明白事件流,必須先懂的這幾個知識點
事件冒泡
事件捕獲
先來看一個有趣的問題, 這是 4 代瀏覽器(IE4)開發團隊遇到一個的問題:
What part of the Webpage owns a specific event?
頁面的哪一部分會擁有某個特定的事件?
要想明白這個問題可以想象成在一個頁面上畫了一組同心圓, 當手指放在中間時,它不僅在一個圓圈內,而且在所有的圓圈內。
如下圖所示:
這就是瀏覽器事件的工作原理, 當你點擊一個按鈕時, 不僅單擊按鈕,還單擊包含的容器和整個頁面。
事件生命周期, 分為三個階段: capturing (捕獲), target (目標), bubbling (冒泡). 而這三個階段也是構成事件流基本部分.
這種處理思想, 在現在流行 NodeJS 后端框架 Koa 對中間件的處理模式也是基于這種, 熟悉Koa或許對這洋蔥圖并不會陌生:
先來熟悉事件的模型:
Javascript Events: Event bubbing (事件冒泡)事件冒泡: 既事件開始由最具體的元素接收,然后逐級向上傳播最后到達 Document 對象 或 window 上.
先來看一個簡單的示例, 代碼如下:
...... Press here.
var target = document.getElementById("demo"); window.addEventListener("click", function(){ console.log("window bubbling"); }); document.addEventListener("click", function(){ console.log("document bubbling"); }); document.documentElement.addEventListener("click", function(){ console.log("html bubbling"); }); document.body.addEventListener("click", function(){ console.log("body bubbling"); }); target.addEventListener("click", function(){ console.log("target bubbling"); });
控制臺打印輸出如下:
"target bubbling" "body bubbling" "html bubbling" "document bubbling" "window bubbling"
當一個div 被點擊, 這點擊事件發生的順序如下:
div
body
html
Document
Window (現在瀏覽器中, IE9+, Firfox, Chrome 等)
從執行順序來說, click事件首先在 div 元素上觸發, 然后沿著DOM Tree 向上傳播, 在路徑上的每個節點上觸發,直到它到達文檔對象(或Window對象)。
Javascript Events: Event Capturing (捕獲)事件捕獲 是另外一種事件流模型, 最先由 Netscape Browser 引入.
根據上面的的模型, 剛好與前面冒泡相反. 根據該模型,最不特定(最外層)的節點首先接收事件,而最特定(目標元素)的節點最后接收事件.
它設計的目標就是在事件到達目標之前,事先進行攔截.
參考前面的示例, 修改下監聽方式, 代碼修改如下:
var target = document.getElementById("demo"); window.addEventListener("click", function(){ console.log("window Capturing"); }, true); document.addEventListener("click", function(){ console.log("document Capturing"); }, true); document.documentElement.addEventListener("click", function(){ console.log("html Capturing"); }, true); document.body.addEventListener("click", function(){ console.log("body Capturing"); }, true); target.addEventListener("click", function(){ console.log("target Capturing"); }, true);
打印的結果:
"window bubbling" "document bubbling" "html bubbling" "body bubbling" "target bubbling"
當點擊 div 元素, 按照如下順序來進行廣播事件.
Window (現在瀏覽器中, IE9+, Firfox, Chrome 等)
Document
Document
body
div
注意:
DOM0 級中默認就是使用冒泡的方式, 不支持捕獲的. 所以在 DOM2 級中通過 addEventListener 提供的第三個參數來控制使用哪種事件流來處理.Javascript Events: DOM Event Flow (事件流)
根據上面兩種模型, 可以總結完整事件流應該向如下:
DOM Level 2 Events 指定的事件流模型分為三個階段:
Event Capturing Phase (事件捕獲階段)
At the target (目標階段)
Event Bubbling Phase (事件冒泡階段)
從上面流程圖中, 首先發生的是 事件捕獲階段 為截獲事件提供了機會, 再到目標階段 然后進入 事件冒泡階段, 可以在這個階段對事件進行響應.
引用前面的例子, 點擊 DIV 元素時, 事件將按照上圖順序進行觸發.
這就是完整的事件流, 內容看起來挺多的, 實際上一句話就概述事件流.
用來描述事件發生順序(頁面接收事件順序).事件處理方式
通過前面的知識點, 事件就是表示用戶或瀏覽器自身執行某種動作. 例如: click, dbclick, load, unload, mouseover,mouseout, mouseenter ,mouseleave 等等, 這些都是事件的名字. 而響應并處理某個事件的函數稱為 事件處理程序.
常見事件處理方式包括如下幾種:
HTML 事件處理程序
DOM0 級處理程序
DOM2 級處理程序
HTML 事件處理程序直接來看個簡單示例:
這種模式, 可以理解為 CSS 行內樣式,
直接在 HTML 元素上綁定相應事件名, 并指定對應的事件處理程序. 從前面語法來說, 事件處理程序是一個函數, 上面傳遞是一個語句, 也就是說默認執行時, JS引擎會進行相應處理. 等價于這種方式:
如果把事件處理函數直接以這種內聯值的方式提供, 應該注意不能值中指定未經轉義的HTML語法字符 例如: 和號(&) 、雙引號("") 等等, 否則會解析出錯.
如上面示例,如果想使用雙引號, 必須這么處理.
這種對于簡單語句還行, 除了提供元素屬性值的方式, 那么有木有其它方式咧? 答案:當然有 , HTML事件處理程序可以調用在頁面其它地方定義的腳本.
示例如下:
通過這種方式指定事件處理程序具有一些特別的地方.
前面講述事件處理程序引擎在執行時, 默認封裝函數. 在封裝的函數中包含一個局部變量 event, 也就是事件對象.
// => 等價
該函內部 this 指針表示是當前的目標對象
// this === [object HTMLInputElement] === input元素
這樣可以通過 this來獲取目標元素相關的內容了. 比如取值
為什么可以簡寫方式, input 對象是存在于當前函數的作用域鏈中(scope). 為了調試方式, 把上面方式作如下改變
其實理解上面那句話, 我們可以借助開發這工具來協助理解:
從執行棧(Call Stack)可以看出, 證明前面說的默認會創建封裝函數 .
從 Scope 作用鏈中可以看出, 前面說的 input 對象會被添加在當前匿名函數執行的作用域鏈上.
根據開發工具來看, 我們是可以模擬引擎幫做的事情, 偽代碼如下:
function () { with(document) { with(input){ // dosomething console.log(value); } } }
本質通過 with 來擴展作用域.
上面把基本內容說, 那么這種通過HTML事件處理程序有什么問題么 ?
時差問題
事件處理程序必須優先元素加載, 有可能DOM加載出來, 用戶就開始操作, 此時事件處理程序可能未加載導致報錯
前面提及擴展程序的作用域鏈可能在不同瀏覽器中兼容不一樣,導致程序出現錯誤
視圖和行為偶合在一起,也就是說事件處理程序修改相應JS部分、HTML部分都需要去修改.
引用前面的示例:
如果用戶想把函數名為: "onClick", 這時是不是需要同時去修改.
DOM0 級事件處理程序通過 JavaScript 來指定事件的處理程序, 也就是將一個事件處理程序賦值給一個元素屬性(事件類型).
語法
element.onclick = function(){}
先看個簡單示例:
上述就是 DOM0 級事件處理程序應用.
使用 DOM0級 不同于 HTML事件處理程序 優點在于:
把視圖和行為進行解耦.
更容易讓人理解(作用域的各種問題)
DOM0 級添加事件有哪些特點由于通過 target.onclick 這種方式是不是很熟悉, object.property , 那么意味這添加事件處理程序(方法) 屬于當前 DOM元素的, 相應其內部 this 指向當前元素.
打印輸出結果為:
"ClickMe"
從結果來看,是符合前面說的.
大家是否記得前面說事件流 提及事件兩個階段冒泡 和 捕獲, 那么通過 DOM0級別 添加的事件會發生那個階段 ? 冒泡
通過簡單示例來看看:
打印輸出結果:
"input bubbling" "body bubbling"
細心讀者發現, 在講解 事件捕獲 時提及了, 事件捕獲是 DOM2 級才進行引入的. 也驗證現在說法.
移除事件處理程序簡單粗暴, 也只能這樣.
target.onclick = null;同一個事件名添加多個處理程序
DOM0級中, 使用onclick 方式添加事件處理程序是不支持添加多個, 假如有這樣場景需要自己去擴充, 現在瀏覽器中基本使用 DOM級非常少了, 所以不必擔心.
簡單實現如下:
var target = document.querySelector("input"); function handleOne() { console.log("handle one"); } function handleTwo() { console.log("handle two"); } function addEvent(eventName, target, handle) { var map = target.map ? target.map : target.map = {}; if (typeof target.onclick === "function") { target.map[eventName].push(handle); } else { target.onclick = function () { map[eventName].every(function (fn) { return fn(); }); }; if (!map[eventName]) { map[eventName] = []; } map[eventName].push(handle); } } function removeEvent(eventName, target, handle) { var map = target.map; var list = []; if(!map){return;} if(list = map[eventName]) { var index = list.indexOf(handle); list.splice(index, 1); } } addEvent("onclick", target, handleOne); addEvent("onclick", target, handleTwo); removeEvent("onclick", target, handleOne)
兩個小細節:
為什么迭代循環時為什么使用 every, 而不是用 forEach 、map、some; 這里先預留一個終止執行隊列里面的條件, every 只有每一個數組元素滿足條件時才會往下執行, 否則直接終止循環.
因為添加時直接把事件處理程序添加在一個數組中, 刪除時只能通過函數引用; 所以在 addEvent 時不能通過匿名函數的方式. 否則無法被移除.
兼容性
所有瀏覽器都支持DOM2 級事件處理程序
在DOM2級事件定義了兩個方法, 用來指定和刪除事件處理程序的操作, 分別是 addEventListener 和 removeEventListener.
語法:
target.addEventListener(type, listener[, options]); target.addEventListener(type, listener[, useCapture]); target.removeEventListener(type, listener[, options]); target.removeEventListener(type, listener[, useCapture]);
type
表示事件的類型的字符串
listener
事件處理程序必須是一個實現 EventListener 接口的對象, 或 一個函數. 兩者實質都是用來接收并處理觸發的事件.
options 可選
主要跟 listener 相關的
capture Boolean 表示該 listener 事件處理程序是否在捕獲階段被觸發.
once Boolean 表示 listener 在添加之后最多只調用一次。如果是 true, listener 會在其被調用之后自動移除。
passive: Boolean, 設置為true時, 表示 listener 永遠不會調用 preventDefault(). 如果 listener 仍然調用了這個函數, 客戶端將會忽略它并拋出一個控制臺警告.
useCapture 可選
默認值: false 表示在冒泡階段調用事件處理函數.
小提示:
從參數簽名來看 options 與 useCapture 是不能同時配置, 這就是為什么 options 中會提供 capture 的原因. 這樣其實帶來另外一個好處, 可以更精確控制到每一個事件處理程序的觸發時機
先通過一個簡單的示例來把上面知識點應用一下,示例如下:
控制臺依次輸出:
// 第一次點擊時 "one handle" "two handle" "capture handle" "once handle" "window" // 第二次點擊時 "one handle" "two handle" "capture handle" "window"
從輸出來看:
默認是在冒泡階段來觸發事件處理程序 (listener); 需要捕獲階段攔截可以通過把 useCapture 改為 false.
使用DOM2級 默認支持針對同一個事件類型添加多個處理程序. DOM0 級 不支持的.
事件觸發的順序按照添加順序執行.
在講述語法時, listener 可以是一個實現 EventListener 接口的對象. 上面指定 listener 時我們可以使用如下方式:
一般推薦使用前者;
事件處理程序執行的作用域事件處理程序中 this 指向當前對象(input)
控制臺輸出:
"ClickMe"移除事件處理程序
移除事件處理程序通過 removeListener 方法. 從語法來看, 傳入參數與添加時傳入的參數一樣, 也就是說事件處理程序(函數)就不能為匿名函數, 否則無法移除
錯誤的使用方式:
控制臺輸出:
"handleListener"
正確使用方式:
總結:
指定事件類型時,使用 click 而不是 onclick (click類型為例)
允許支持同一個事件名添加多個處理程序, 執行順序為添加順序.
事件處理程序依附的對象, 作為處理函數執行時的作用域.
移除事件處理程序時, 不能使用匿名函數.
兼容性:
IE9、Firefox、Safari、Chrome和 Opera 支持DOM2級事件處理程序
從兼容性來看,那么在IE中有沒有類似 addEventListener 和 removeEventListener 事件處理程序? 有
IE 中添加事件處理程序IE實現了類似 DOM 中類似的兩個方式: attachEvent(), detachEvent(). 這兩個方法默認接收兩個參數.
語法格式(Non-standard):
attachEvent(type, listener) detachEvent(tyoe, listener)
先來看個具體示例(IE中運行):
控制臺輸出結果:
// 點擊 "移除事件處理程序按鈕之前" "handleListener" "undefined" // 點擊之后, 再執行沒有輸出(事件處理程序被移除)
注意:
事件類型名稱(type) 和 DOM0級一樣需要指定完整屬性名 onclick
移除事件處理程序時, 傳入參數必須一樣
attachEvent 和 detachEvent 不支持捕獲, 也就是說在被添加到冒泡階段
事件處理程序在全局作用域中執行
兼容
attachEvent 和 detachEvent 只能 IE10以及一下版本中使用.IE事件處理程序與DOM0級的區別
在IE中使用 attachEvent detachEvent, 與 DOM0級不同的地方在于事件處理程序執行時作用域不一樣. 前面講到過DOM0和 DOM2事件處理程序都會在所屬元素的作用域內運行; 從上面 this.id 打印結果來看, attachEvent 在全局作用域(window)中運行.
到目前為止,把添加事件處理程序方式都一一列舉完. 具體引用那種可以根據實際需求選擇.
Event 對象常見應用是伴隨著事件執行過程中的一個產物, 這個稱之為 event (事件對象). 該事件對象中包含很多跟事件以及用戶相關的內容(例如: 事件類型、事件對象、用戶點擊位置、移動位置等等)
下面羅列下 event object 包含常用屬性和方法:
名稱 | 描述 | 返回值 |
---|---|---|
type | 指定的事件名稱(例如 click, mouseout) | string |
target | 表示要觸發事件的元素 | HTMLElement |
currentTarget | 事件處理程序調用時的元素 | HTMLElement |
eventPhase | 事件當前處于事件的那個階段; 1 捕獲 2 事件目標 3 冒泡階段 | number |
bubbles | 表示當前事件對象是否會向 DOM 樹上層元素冒泡 | boolean |
cancelable | 事件的 cancelable 屬性表明該事件是否可以被取消默認行為, 如果該事件可以用 preventDefault() 可以阻止與事件關聯的默認行為,則返回 true,否則為 false | boolean |
timeStamp | 返回事件發生時的時間戳. | string |
stopPropagation() | 阻止捕獲和冒泡階段中當前事件的進一步傳播 如果 bubbles為ture可以使用 | void |
stopImmediatePropagation() | 阻止事件冒泡并且阻止相同事件的其他偵聽器被調用 DOM3級中添加 | void |
preventDefault() | 阻止事件默認行為,事件依然會傳播除非遇到 stopPropogation 或 stopImmediatePropogation 如果 cancelable為turu則可以使用這個方法 | void |
defaultPrevented | 返回一個布爾值,表明當前事件是否調用了 event.preventDefault()方法 DOM3級中添加 | boolean |
回顧前面講解添加事件處理程序(HTML事件處理程序、DOM0 、 DOM2) 分別看怎么來獲取
HTML事件處理程序時:
可以通過這兩種方式來獲取事件對象
DOM0 和 DOM2
可以通過參數的方式來獲取, 這是推薦的獲取方式. 除了上述幾種方式外, 其實可以通過 window.event 來獲取表示當前正觸發的事件對象, 不過這種方式不是標準(IE), 所以不推薦使用.
event 屬性和方法使用 target 和 currentTarget引用前面的示例, 講述下 target 和 currentTarget
控制臺輸出:
true true
結合前面對屬性描述, event.target 表示當前事件觸發的對象. event.currentTarget 表示當前事件處理程序執行時的對象(上下文). 從打印結果來看,當事件處理程序直接綁定在目標元素上時,這兩者相同的.
什么時候不一樣咧?
true false
符合預期, 上面代碼中把事件處理程序綁定在了 div#container 元素上, 這時 currentTarget 是當前的事件處理程序綁定的對象.
typetype 用于獲取當前事件類型之外, 可以作如下用途, 當希望在同一個事件處理程序中處理多個不同事件類型時, 如下:
cancelable、preventDefault() 、 defaultPrevented
具體上個介紹可以參照前面的表格里面描述, 為什么把這三個結合在一塊, 彼此間是有關聯的.
先來看看具體示例:
控制臺輸出:
true // 表示可以被阻止 true // 表示調用 preventDefault()
什么情況下會有 event.cancelable 返回 false ? 現有 focus、blur、
focusin、focusout 等不允許阻止, 或一些自定義的事件
控制臺輸出:
false // 表示可以被阻止 false // 表示調用 preventDefault()bubbles、stopPropagation()、stopImmediatePropagation()
具體上個介紹可以參照前面的表格里面描述, 為什么把這三個結合在一塊, 彼此間是有關聯的.
先來看看具體示例:
控制臺輸出:
"handleOne" true "handleTwo"
從輸出結果來看, event.stopPropagation() 只會阻止事件冒泡, 并不會阻止同類型多個事件處理程序的執行.
再來看看 stopImmediatePropagation() 修改如下:
控制臺輸出:
"handleOne" true
event.stopImmediatePropagation() 阻止事件冒泡的同時當前事件類型下其它事件處理程序的執行. 如果 handleOne 中調用 stopImmediatePropagation, 那么 handleTwo就不會再執行.
eventPhase當前事件處于那個階段
// PhaseType CAPTURING_PHASE = 1; // 捕獲 AT_TARGET = 2; // 事件目標 BUBBLING_PHASE = 3; // 冒泡階段
先來看看簡單示例:
前面講解都是DOM標準,也就是所有瀏覽器廠商都支持的, 那么來看看 IE9 以下版本中會不會有什么不一樣.
IE中事件對象我們也從添加事件處理程序的維度出發, 看看有什么不一樣.
HTML事件處理程序正常彈出 click, 這與前面說到的一樣.
DOM0以前面的示例來演示:
控制條輸出:
undefined [object Object]
測試環境: IE8以及一下的版本
在 DOM0 級事件處理程序中, 事件對象添加在 window 上的屬性 event
attachEvent引用上面代碼, 使用 attachEvent 來添加事件,并獲取 event
var target = document.getElementById("button"); target.attachEvent("onclick", function(e){ console.log(e); })事件對象相關的屬性
名稱 | 描述 | 返回值 |
---|---|---|
type | 指定的事件名稱(如 click, mouseout) | string |
srcElement | 表示要觸發事件的元素(與DOM中 target) | HTMLElement |
returnValue | 默認值為 true, 但將其設置為 false, 就可以取消事件的默認行為(與DOM中 preventDefault()) | Boolean |
cancelBubble | 默認值為 false, 但將其設置為 ture. 就取消冒泡行為(與DOM中的 stopPropagation()) | Boolean |
看看具體示例:
當點擊按鈕時, 輸出結果:
"true"
當點擊超鏈接時, 輸出結果:
"container"
從點擊超鏈接來看, e.returnValue 只會阻止默認行為, 事件對象依然進行會冒泡.
事件、目標、事件冒泡、事件默認行為獲取方式-匯總添加事件程序方式 | 語法 | 兼容 |
---|---|---|
HTML事件處理程序 | < element onclick="alert(event)" > | All |
DOM0 | element.onclick = function(event){} | All |
DOM2 | addEventListener | IE9+,Chrome,Firfox,Safari,360 |
IE | attachEvent | IE11< |
獲取事件對象 | 方式 | 例子 |
---|---|---|
HTML事件處理程序 | event 變量 | < element onclick="alert(event)" > |
DOM0 | 事件處理程序參數 、IE9以下通過 windiw.event | function(event){}、window.event |
DOM2 | 事件處理程序參數 | function(event){} |
IE | 事件處理程序參數 | function(event){} |
獲取目標對象方式 | 屬性名 | 兼容 |
---|---|---|
標準 | target | IE9+,Chrome,Firfox,Safari,360 |
IE版本 | srcElement | IE9< |
阻止事件冒泡 | 屬性或方法 | 兼容 |
---|---|---|
標準 | stopPropagation()、stopImmediatePropagation() | IE9+,Chrome,Firfox,Safari,360 |
IE版本 | cancelBubble | IE9< |
阻止默認行為 | 屬性或方法 | 兼容 |
---|---|---|
標準 | preventDefault() | IE9+,Chrome,Firfox,Safari,360 |
IE版本 | returnValue | IE9< |
var EventUtil = { addHandler: function(element, type, handler) { if(element.addEventListener) { element.addEventListener(type, handler); }else if(element.attachEvent){ element.attachEvent("on" + type, handler); }else { element["on" + type] = handler; } }, removeHandler: function(element, type, handler){ if(element.addEventListener) { element.removeEventListener(type, handler); }else if(element.attachEvent){ element.detachEvent("on" + type, handler); }else { element["on" + type] = null; } }, getEvent: function(event){ return event? event : window.event; }, getTarget: function(event){ return event.target ? event.target:event.srcElement; }, stopPropagation: function(event){ if(event.stopPropagation) { event.stopPropagation(); }else { event.cancelBubble = true; } }, preventDefault: function(event){ if(event.preventDefault) { event.preventDefault(); }else { event.returnValue = false; } } }
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/105188.html
摘要:繼承接口對象不僅實現了接口,也實現了接口,用來標識當前窗口內的文檔節點。繼承接口描述了所有相同種類的元素所普遍具有的方法和屬性。 由于工作中一直在用框架來解決問題,在平時對dom的關注也比較少(特別像angular這種自己封裝了一層視圖層的框架,并不建議直接操作DOM),所以dom相關的知識也忘的差不多了,這次做公司產品的官網,沒有太多的交互和功能,直接用了原生js,正好借此整理一下遺...
摘要:事件相關內容當用戶與瀏覽器發生的一些交互時如果希望去獲得用戶行為就需要借助事件來完成事件部分內容在中重要性不言而喻羅列需要了解與事件相關的知識如下這也是面試中遇到的問題事件的級別事件模型事件流事件處理程序描述事件捕獲冒泡的具體流程對象常見的 DOM事件相關內容 當用戶與瀏覽器發生的一些交互時, 如果希望去獲得用戶行為, 就需要借助事件來完成. 事件部分內容在 JS中重要性不言而喻. ...
摘要:在標簽中添加屬性,本質上是跟在標簽里面寫屬性時一樣的,所以屬性值最終都會編譯為字符串類型。這個節點包括很多,比如,以及一些方法等方法。一個對象有很多,該集合名字為,里面有其他以及,里面有很多。 一、變量類型和計算 JS中使用typeof能得到哪些類型 變量類型 值類型:變量本身就是含有賦予給它的數值的,它的變量本身及保存的數據都存儲在棧的內存塊當中 引用類型:引用類型當然是分配到...
閱讀 3478·2023-04-26 02:00
閱讀 3078·2021-11-22 13:54
閱讀 1699·2021-08-03 14:03
閱讀 709·2019-08-30 15:52
閱讀 3085·2019-08-29 12:30
閱讀 2420·2019-08-26 13:35
閱讀 3364·2019-08-26 13:25
閱讀 3001·2019-08-26 11:39