前端最基礎的就是 HTML+CSS+Javascript
。掌握了這三門技術就算入門,但也僅僅是入門,現在前端開發的定義已經遠遠不止這些。前端小課堂(HTML/CSS/JS
),本着提升技術水平,打牢基礎知識的中心思想,我們開課啦(每週四)。
前面我們已經基本掌握常規的語法語義,以及基本的使用方法。接下來我們講深入進去了解其中內在的原理。
今天我們要講什麼?
- 事件機制
- 事件對象(Event)
- event loop
DOM (與事件的關係,看不看無所謂)
DOM(Document Object Model——文檔對象模型)是用來呈現以及與任意 HTML 或 XML文檔交互的 API。DOM 是載入到瀏覽器中的文檔模型,以節點樹的形式來表現文檔,每個節點代表文檔的構成部分(例如:頁面元素、字符串或註釋等等)。
DOM 是萬維網上使用最爲廣泛的 API 之一,因爲它允許運行在瀏覽器中的代碼訪問文件中的節點並與之交互。節點可以被創建,移動或修改。事件監聽器可以被添加到節點上並在給定事件發生時觸發。
DOM 並不是天生就被規範好了的,它是瀏覽器開始實現JavaScript時纔出現的。這個傳統的 DOM 有時會被稱爲 DOM 0。現在, WHATWG 維護DOM現存標準。
-- MDN
既然 DOM
有版本,那麼在他的環境上事件的支持也是有版本的。文檔
DOM 事件(0 級)
body.onclick
這種定義方式的。
- 不可以多次監聽事件,因爲是賦值的方式,下次賦值會覆蓋。
- 只可以在冒泡階段觸發
DOM 事件(2 級)
addEventListener
方式定義的。
- 可以多次監聽,切按監聽順序執行回調(有序)
-
取消監聽需要同一引用的函數。舉個栗子
// 錯誤案例,兩個方法不是同一引用,導致清除不掉 document.addEventListener('click', function(){}) document.removeEventListener('click', function(){}) // 正確案例,同一引用,可以清除。 function documentClick(){} document.addEventListener('click', documentClick) document.removeEventListener('click', documentClick)
- 可以選擇觸發階段(冒泡&捕獲)
capture
事件機制
標準事件:EMCAScript 標準規定事件流包含三個階段,分別爲事件捕獲階段,目標階段,事件冒泡階段。
先存個代碼,之後的例子我們用這個例子。測試看我這裏的 DEMO
<html onclick="alert('html')">
<body onclick="alert('body')">
<a onclick="alert('a')">click</a>
</body>
</html>
事件捕獲階段
捕獲階段:由外到內,觸發規律爲 html > body > a
。
如果想在捕獲階段就觸發,需要傳入參數 {capture: true}
事件冒泡階段
冒泡階段:由內到外,觸發規律爲 a > body > html
這個階段執行是 W3C
默認的,等價於 {capture: false}
事件執行順序
圖片來源-https://www.w3.org/TR/DOM-Lev...
事件的捕獲階段 > 處於目標階段 > 事件的冒泡階段 > 事件的默認行爲
這裏爲什麼要強調這個順序呢?
- 因爲默認行爲是在最後面,所以我們都可以用
e.preventDefault()
來阻止。 - 基於上條的阻止默認事件。在移動端滑動時,阻止默認事件需要手動設置
passive
爲false
。passive: Boolean
,設置爲true
時,表示listener
永遠不會調用preventDefault()
。如果listener
仍然調用了這個函數,客戶端將會忽略它並拋出一個控制檯警告。 - 我們真正單擊的元素的事件觸發不在冒泡和捕獲階段,而在目標階段觸發。 DEMO-冒泡&捕獲階段觸發事件,可以看到,他是通過定義時的先後順序來觸發的。
事件對象(Event)
事件對象(屬性&方法)
key | 類型 | 描述 |
---|---|---|
bubbles | boolean | 是否冒泡 |
cancelable | boolean | 是否可以取消的默認動作。 |
currentTarget | Element | 返回其事件監聽器觸發該事件的元素。(this 的真實指向) |
eventPhase | Intenger | 返回事件傳播的當前階段 |
target | Element | 返回觸發此事件的元素。(事件的目標節點) |
timeStamp | Date | 觸發的時間戳 |
type | String | 事件名稱。 |
isTrusted | boolean | 該事件是否是瀏覽器生成(true 代表是,false 代表是開發人員創建 |
preventDefault | Function | 取消事件的默認行爲在 cancelable=true 時有效 |
stopPropagation | Function | 取消事件的捕獲或者冒泡行爲在 bubbles=true 時有效 |
- IE:
event.cancelBubble=true;
//阻止事件冒泡 - IE:
event.returnValue=false;
//阻止事件的默認行爲 - 獲取事件
window.event
事件類型(分類、Event對象之類)
DOM event 子類,根據不同的事件類型,返回的對象會有些許不同,比如 Mouse
類型的,就會有單擊座標之類的。 KeyboardEvent
之類的就會有按鍵之類的。
new 一個事件對象
document.body.onclick=function(e){console.log(e)}
var btn=document.body;
var event= new CustomEvent("click");
btn.dispatchEvent(event);
其實這裏我們可以自定義事件的名稱,然後我們就可以實現一個發佈訂閱的功能。
document.addEventListener("bus", function(e) { console.log(e, e.detail) });
var event = new CustomEvent("bus", {detail: {LN_type: 'lilnong.top'}});
document.dispatchEvent(event);
event loop (事件循環)
首先,我們要牢記一件事情 js 是單線程Event Loop
中文叫事件循環。是瀏覽器內部的一種機制,javaScript 單線程運行時如何不阻塞 UI
。Javascript
有一個 main thread 主線程
和 call-stack 調用棧(執行棧)
,所有的任務都會被放到調用棧(棧採用的是後進先出的規則)等待主線程執行。
任務類別&任務隊列(Task Queue)
在 JavaScript
中,任務被分爲兩種,一種宏任務(MacroTask)也叫Task,一種叫微任務(MicroTask)。
MacroTask(宏任務)
<script>
、setTimeout
、setInterval
、setImmediate
、I/O
、UI Rendering
。
異步任務會在有了結果後,將註冊的回調函數放入任務隊列中等待主線程空閒的時候(調用棧被清空),被讀取到棧內等待主線程的執行。
MicroTask(微任務)
Process.nextTick
(Node獨有)、Promise
、MutationObserver
每個宏任務執行完畢後,會檢查 microTask
隊列是否有回調,會按照先入先出的規則執行,都執行完再執行宏任務,如此循環。
調用棧
棧採用的是後進先出的規則,這裏我們調用 a()
,a()
內部會調用 aa()
, aa()
內部又調用 aa()
。
function a(){return aa()}
function aa(){return aaa()}
function aaa(){return 1}
- a 進棧
- aa 進棧
- aaa 進棧
- aaa 出棧
- ...
事件循環的進程模型
- 選擇任務隊列中最先進入的任務,如果任務隊列爲空,則執行跳轉到微任務(MicroTask)的執行步驟
- 任務設置爲已選擇任務
- 執行任務
- 任務設置爲空
- 運行完成的任務從任務隊列中刪除
-
MicroTasks
步驟:- 進入
MicroTask
檢查點 - 設置
MicroTask
檢查點標誌爲true
-
當事件循環
MicroTask
不爲空時:- 選擇最先進入隊列的任務,
- 設置爲已選擇的任務
- 運行
- 將已經執行完成的
MicroTask
改變狀態 - 移出
MicroTask
。
- 清理IndexDB事務
- 設置
MicroTask
檢查點的標誌爲false。
- 進入
- 更新界面渲染。
- 返回第一步。
舉個栗子(常問無聊題)
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
new Promise(function(reslove){
console.log('Promise-start')
reslove();
}).then(function() {
console.log('Promise-end');
})
console.log('script end');
結構應該沒錯
- 任務入棧(代碼塊)
-
console.log('script start');
棧中,同步代碼,直接輸出 -
function() {console.log('setTimeout');}
入MacroTask
-
new Promise
同步代碼,執行 - 入棧
function(reslove){console.log('Promise-start');reslove();}
- 執行
console.log('Promise-start');
- 出棧
-
.then(function() {console.log('Promise-end');})
進MicroTask
-
console.log('script end');
同步代碼,輸出 - 當前執行完出棧,判斷
MicroTasks
- 執行
console.log('Promise-end');
- 完成所有
MicroTasks
- 渲染 UI
-
MacroTasks
是否有數據? - 執行
MacroTasks
中第一個。 -
console.log('setTimeout');
輸出。
異步事件(消息)
-
DOM
事件 setTimeout
XHR
Promise
總結
-
事件機制
- 當前執行塊
- 當前執行塊的微任務隊列
- 宏任務隊列
- Event 事件級別
- addEventListener 要主要保存 function 的引用,用於解綁
- 隊列,先進先出(想起了梗,吃多了拉)
- 堆棧,先進後出(想起了梗,吃多了吐)
- 觸發階段 捕獲>目標>冒泡
- Event 對象,針對不同的類型,有自己獨特的屬性。
微信公衆號:前端linong
初級階段文章目錄
- 前端培訓-初級階段(17) - 數據存儲(cookie、session、stroage)
- 前端培訓-初級階段(13) - 正則表達式
- 前端培訓-初級階段(13) - 類、模塊、繼承
- 前端培訓-初級階段(13) - ECMAScript (內置對象、函數)
- 前端培訓-初級階段(13) - ECMAScript (語法、變量、值、類型、運算符、語句)
- 前端培訓-初級階段(13、18)
- 前端培訓-初級階段(9 -12)
- 前端培訓-初級階段(5 - 8)
- 前端培訓-初級階段(1 - 4)