基礎知識
在文件、瀏覽器、標籤元素等元素在特定狀態下觸發的行為即為事件,比如使用者的單擊行為、表單內容的改變行為即為事件,我們可以為不同的事件定義處理程式。Js
使用非同步事件驅動的形式管理事件。
事件型別
Js
為不同的事件定義的型別,也可以稱為事件名稱。
事件目標
事件目標指產生事件的物件,比如<a>
標籤被點選那麼<a>
標籤就是事件目標。元素是可以巢狀的,所以在進行一次點選行為時可能會觸發多個事件目標。
處理程式
事件的目的是要執行一段程式碼,我們稱這類程式碼為事件處理(監聽)程式。當在物件上觸發事件時就會執行定義的事件處理程式。
HTML繫結
可以在html
元素上設定事件處理程式,瀏覽器解析後會繫結到DOM
屬性中
<button onclick="alert('彈窗')">點我出現彈窗</button>
往往事件處理程式業務比較複雜,所以繫結方法或函式會有常見
繫結函式或方法時需要加上括號
<body>
<button onclick="show()">點我出現彈窗</button>
</body>
<script>
function show() {
alert("彈窗");
}
</script>
繫結類的靜態方法
<body>
<button onclick="User.show()">點我出現彈窗</button>
</body>
<script>
class User{
static show(){
alert("彈窗");
}
}
</script>
在繫結事件的HTML
的元素上,可以進行引數的傳遞
this:即事件目標本身
event:事件物件
<body>
<button onclick="show(this,event)">點我</button>
</body>
<script>
function show(self,event){
console.log(self); // <button onclick="show(this,event)">點我</button>
console.log(event); // 包含很多資訊
}
</script>
DOM繫結
也可以將事件處理程式繫結到DOM
屬性中
使用
setAttribute
方法設定事件處理程式無效屬性名區分大小寫
使用普通函式進行DOM
繫結時,this
即為事件目標本身
<body>
<button>點我</button>
</body>
<script>
undefined
let button = document.querySelector("button");
button.onclick = function () {
console.log(this) // <button>點我</button>
}
</script>
在使用DOM
繫結時不推薦使用箭頭函式,因為這會使this
的指向為window
或undefined
,但是我們可以通過事件物件提取出事件目標本身
<body>
<button>點我</button>
</body>
<script>
let button = document.querySelector("button");
button.onclick = (event)=>{
console.log(event.target) // <button>點我</button>
}
</script>
使用DOM
繫結時不允許對同一事件進行多次處理,只會依照最後的處理程式為準
<body>
<button>點我</button>
</body>
<script>
let button = document.querySelector("button");
button.onclick = function () {
console.log(this); // 不列印
}
button.onclick = function () {
alert("彈窗"); // 列印
}
</script>
事件監聽
使用HTML
與DOM
繫結都有缺陷,建議使用新的事件監聽繫結方式
方法 | 說明 |
---|---|
addEventListener | 新增事件處理程式 |
removeEventListener | 移除事件處理程式 |
addEventListener
使用addEventListener
新增事件處理程式
transtionend / DOMContentLoaded
等事件型別只能使用addEventListener
處理同一事件型別設定多個事件處理程式,按設定的順序先後執行
也可以對未來新增的元素繫結事件
引數說明如下
引數 | 說明 |
---|---|
引數1 | 事件型別 |
引數2 | 事件處理程式 |
引數3 | 定製選項 |
引數3的定製項
once:true
:只執行一次事件
capture:true/false
:捕獲階段傳播到該EventTarget
時觸發
passive:true
:listener
永遠不會呼叫preventDefault()
使用addEventListener
可對同一事件進行多次監聽
<body>
<button>點我</button>
</body>
<script>
let button = document.querySelector("button");
button.addEventListener("click", function () {
console.log(this); // 列印
})
button.addEventListener("click",function () {
alert("彈窗"); // 列印
})
</script>
物件繫結
如果事件處理程式可以是物件,物件的 handleEvent
方法會做為事件處理程式執行。下面將元素的事件統一交由物件處理
<body>
<div style="width: 300px;height: 300px;background: red;"></div>
</body>
<script>
class DivEvent {
handleEvent(e){
this[e.type](e.target); // e是事件物件
}
click(self) {
console.log("滑鼠點選事件",self); // self即為事件目標 div標籤
}
mouseover(self) {
console.log("滑鼠移動事件",self);
}
}
let div = document.querySelector("div");
let divEvent = new DivEvent();
div.addEventListener("click",divEvent);
div.addEventListener("mouseover",divEvent);
</script>
removeEventListener
使用removeEventListener
刪除繫結的事件處理程式
事件處理程式單獨定義函式或方法,這可以保證事件處理程式是同一個
以下示例中,每次點選<div>
都會令其內容+1,當點選刪除事件按鈕後點選無效。
<body>
<div style="width: 100px;height: 100px;background: red;color: #fff;text-align: center;line-height: 100px;">
</div>
<button>刪除事件</button>
</body>
<script>
function add(event) {
if (!event.target.innerText) {
event.target.innerText = 1;
} else {
event.target.innerText++;
}
}
document.querySelector("div").addEventListener("click", add);
document.querySelector("button").addEventListener("click", (event) => {
document.querySelector("div").removeEventListener("click",add)
});
</script>
注意事項
通過HTML
與DOM
進行事件處理程式的繫結,需要在事件名前加入on
,比如click
就變為onclick
,copy
變為oncopy
。
而使用事件監聽的方式則不需要加上on
,這與jQuery
的做法如出一轍。
事件物件
執行事件處理程式時,會產生當前事件相關資訊的物件,即為事件物件。
系統會自動做為引數傳遞給事件處理程式。
大部分瀏覽器將事件物件儲存到
window.even
t中有些瀏覽器會將事件物件做為事件處理程式的引數傳遞
事件物件常用屬性如下:
屬性 | 說明 |
---|---|
type | 事件型別 |
target | 事件目標物件,冒泡的父級通過該屬性可以找到在哪個元素上執行了事件 |
currentTarget | 當前執行事件的物件 |
timeStamp | 事件發生時間 |
x | 相對視窗的X座標 |
y | 相對視窗的Y座標 |
clientX | 相對視窗的X座標 |
clientY | 相對視窗的Y座標 |
screenX | 相對計算機螢幕的X座標 |
screenY | 相對計算機螢幕的Y座標 |
pageX | 相對於文件的X座標 |
pageY | 相對於文件的Y座標 |
offsetX | 相對於事件物件的X座標 |
offsetY | 相對於事件物件的Y座標 |
layerX | 相對於父級定位的X座標 |
layerY | 相對於父級定位的Y座標 |
path | 冒泡的路徑 |
altKey | 是否按了alt鍵 |
shiftKey | 是否按了shift鍵 |
metaKey | 是否按了媒體鍵 |
window.pageXOffset | 文件參考視窗水平滾動的距離 |
window.pageYOffset | 文件參考視窗垂直滾動的距離 |
冒泡捕獲
冒泡行為
標籤元素是巢狀的,在一個元素上觸發的事件,同時也會向上執行父級元素的事件處理程式,一直到HTML
標籤元素。
大部分事件都會冒泡,但像
focus
事件則不會
event.target
可以在事件中(包括父級元素中)得到事件目標元素即最底層的產生事件的物件
event.currentTarget == this
即當前執行事件的物件
以下示例有標籤的巢狀,並且父子標籤都設定了事件,當在子標籤上觸發事件事會冒泡執行父級標籤的事件
簡而言之,如果巢狀的標籤設定有相同的事件,會按照最外層的標籤處理程式為準。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
body {
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
section {
width: 200px;
height: 200px;
display: flex;
justify-content: center;
align-items: center;
background: blueviolet;
}
article {
width: 100px;
height: 100px;
background: deeppink;
}
</style>
</head>
<body>
<section>
<article></article>
</section>
</body>
<script>
document.body.addEventListener("click", (evnet) => {
event.target.style.background = "green"; // 爺爺body是綠色
});
document.querySelector("section").addEventListener("click", (event) => {
event.target.style.background = "red"; // 爸爸section是紅色
});
document.querySelector("article").addEventListener("click", (event) => {
event.target.style.background = "deepskyblue"; // 孫子deepskyblue是藍色
});
</script>
</html>
阻止冒泡
冒泡過程中的任何事件處理程式中,都可以執行 event.stopPropagation()
方法阻止繼續進行冒泡傳遞
event.stopPropagation()
用於阻止冒泡如果同一型別事件繫結多個事件處理程式
event.stopPropagation()
只阻止當前的事件處理程式
event.stopImmediatePropagation()
阻止事件冒泡並且阻止相同事件的其他事件處理程式被呼叫,也就是說不僅僅是當前的事件處理程式不會冒泡了,所有關於該標籤的所有事件處理程式都會阻止冒泡的發生。所以推薦使用event.stopImmediatePropagation()
為上圖新增阻止冒泡
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
body {
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
section {
width: 200px;
height: 200px;
display: flex;
justify-content: center;
align-items: center;
background: blueviolet;
}
article {
width: 100px;
height: 100px;
background: deeppink;
}
</style>
</head>
<body>
<section>
<article></article>
</section>
</body>
<script>
document.body.addEventListener("click", (evnet) => {
event.target.style.background = "green"; // 爺爺body是綠色
});
document.querySelector("section").addEventListener("click", (event) => {
event.stopImmediatePropagation();
event.target.style.background = "red"; // 爸爸section是紅色
});
document.querySelector("article").addEventListener("click", (event) => {
event.stopImmediatePropagation();
event.target.style.background = "deepskyblue"; // 孫子deepskyblue是藍色
});
</script>
</html>
事件捕獲
事件執行順序為 捕獲 > 事件目標 > 冒泡階段執行,在向下傳遞到目標物件的過程即為事件捕獲。事件捕獲在實際使用中頻率不高。
通過設定第三個引數為
true
或{ capture: true }
在捕獲階段執行事件處理程式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
body {
width: 100vw;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
}
section {
width: 200px;
height: 200px;
display: flex;
justify-content: center;
align-items: center;
background: blueviolet;
}
article {
width: 100px;
height: 100px;
background: deeppink;
}
</style>
</head>
<body>
<section>
<article></article>
</section>
</body>
<script>
document.querySelector("section").addEventListener("click", (event) => {
event.target.style.background = "red"; // 爸爸section是紅色
},{ capture: true }); // 捕獲階段執行事件處理程式,這與阻止冒泡並無關係
document.querySelector("article").addEventListener("click", (event) => {
event.target.style.background = "deepskyblue"; // 孫子deepskyblue是藍色
},{ capture: true }); // 捕獲階段執行事件處理程式,這與阻止冒泡並無關係
</script>
</html>
事件代理
藉助冒泡思路,我們可以不為子元素設定事件,而將事件設定在父級。然後通過父級事件物件的event.target
查詢子元素,並對他做出處理。
在jQuery
中對事件代理的操作極其簡單,但是在原生的JavaScript
中還是有一些複雜的。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
.show{
list-style: none;
border: 1px solid #ddd;
margin:20px;
}
</style>
</head>
<body>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
</body>
<script>
document.querySelector("ul").addEventListener("click", () => {
if(event.target.tagName === "LI"){ // 如果事件目標是li,則新增樣式
event.target.classList.toggle("show");
}
})
</script>
</html>
我們可以將事件代理做成jQuery
那樣
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
section {
width: 200px;
padding: 10px;
display: flex;
justify-content: center;
align-items: center;
flex-flow: column;
outline: 1px solid #ddd;
}
div {
display: flex;
justify-content: center;
align-items: center;
width: 180px;
margin: 10px;
background-color: darkviolet;
color: #fff;
}
</style>
</head>
<body>
<section>
<div>1</div>
<div>2</div>
<div>3</div>
</section>
</body>
<script>
function EventProxy(ProxyElement, ElementType, EventElement, func) {
ProxyElement.addEventListener(ElementType, () => {
if (event.target.tagName == EventElement.toUpperCase()) {
func(event.target);
}
});
}
let section = document.querySelector("section");
// 委託的DOM 事件型別 事件目標 ele即為事件目標
EventProxy(section, "click", "div", (ele) => {
ele.style.backgroundColor = "red";
});
</script>
</html>
模擬出jQuery
的事件代理
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
section {
width: 200px;
padding: 10px;
display: flex;
justify-content: center;
align-items: center;
flex-flow: column;
outline: 1px solid #ddd;
}
div {
display: flex;
justify-content: center;
align-items: center;
width: 180px;
margin: 10px;
background-color: darkviolet;
color: #fff;
}
</style>
</head>
<body>
<section>
<div>1</div>
<div>2</div>
<div>3</div>
</section>
</body>
<script>
Element.prototype.on = function (ElementType, EventElement, func) {
this.addEventListener(ElementType, () => {
if (event.target.tagName == EventElement.toUpperCase()) {
func(event.target);
}
});
}
let section = document.querySelector("section");
// 委託的DOM 事件型別 事件目標 ele即為事件目標
section.on("click", "div", (ele) => {
ele.style.backgroundColor = "red";
});
</script>
</html>
未來元素
未來元素是指後期通過Js
新增的元素,對於這些元素而言我們可以通過父級事件代理的形式讓它們也能進行一些事件的處理,而不用一個一個再去進行事件繫結。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
section {
width: 200px;
padding: 10px;
display: flex;
justify-content: center;
align-items: center;
flex-flow: column;
outline: 1px solid #ddd;
}
div {
display: flex;
justify-content: center;
align-items: center;
width: 180px;
margin: 10px;
background-color: darkviolet;
color: #fff;
}
</style>
</head>
<body>
<section>
</section>
</body>
<script>
Element.prototype.on = function (ElementType, EventElement, func) {
this.addEventListener(ElementType, () => {
if (event.target.tagName == EventElement.toUpperCase()) {
func(event.target);
}
});
}
let section = document.querySelector("section");
let div = document.createElement("div"); // 未來元素,不用繫結任何事件,其父級元素進行事件代理即可
div.innerText = "newElement";
section.append(div); // 新增未來元素
// 委託的DOM 事件型別 事件目標 ele即為事件目標
section.on("click", "div", (ele) => {
ele.style.backgroundColor = "red";
});
</script>
</html>
預設行為
Js
會有些物件會設定預設事件處理程式,比如<a>
連結在點選時會進行跳轉。一般預設處理程式會在使用者定義的處理程式後執行,所以我們可以在我們定義的事件處理程式設計師取消預設事件處理程式的執行。
使用
onclick
繫結的事件處理程式,return false
可以阻止預設行為推薦使用
event.preventDefault()
阻止預設行為
以下示例將展示使用event.preventDefault()
阻止submit
的預設行為。
<body>
<form action="http://www.google.com">
<p><input type="text" name="username"></p>
<button type="submit">提交</button>
</form>
</body>
<script>
let button = document.querySelector("button");
button.addEventListener("click",(event)=>{
event.preventDefault();
console.log("已阻止預設行為...");
})
</script>
視窗文件
事件型別
事件名 | 說明 |
---|---|
window.onload | 文件解析及外部資源載入後 |
DOMContentLoaded | 文件解析後不需要外部資源載入,只能使用addEventListener 設定 |
window.beforeunload | 文件重新整理或關閉時 |
window.unload | 文件解除安裝時 |
scroll | 頁面滾動時 |
例項操作
window.onload
事件在文件解析後及圖片、外部樣式檔案等資源載入完後執行。
推薦程式碼書寫至該事件處理函式中
<script>
window.onload = () => {
console.log("邏輯程式碼...")
}
</script>
DOMContentLoaded
事件在文件標籤解析後執行,不需要等外部圖片、樣式檔案、Js
檔案等資源載入
需要等待前面引入的
CSS
樣式檔案載入解析後才執行只能使用
addEventListener
設定
<script>
window.addEventListener("DOMContentLoaded", () => {
console.log("邏輯程式碼...")
});
</script>
當瀏覽器視窗關閉或者重新整理時,會觸發beforeunload
事件,可以取消關閉或重新整理頁面。
返回值為非空字串時,有些瀏覽器會做為彈出的提示資訊內容
部分瀏覽器使用
addEventListener
無法繫結事件
<script>
window.onbeforeunload = function(e){
return "不要走,10000元寶等您領取!"
};
</script>
window.unload
事件在文件資源被解除安裝時執行,在beforeunload
後執行
不能執行
alert
、confirm
等互動指令發生錯誤也不會阻止頁面關閉或重新整理
<script>
window.addEventListener('unload', function (e) {
localStorage.setItem('name', 'yunya');
});
</script>
滑鼠事件
事件型別
針對滑鼠操作的行為有多種事件型別
滑鼠事件會觸發在Z-INDEX最高的那個元素上
事件名 | 說明 |
---|---|
click | 滑鼠單擊事件,同時觸發 mousedown/mouseup |
dblclick | 滑鼠雙擊事件 |
contextmenu | 點選右鍵後顯示的所在環境的選單 |
mousedown | 滑鼠按下(長按) |
mouseup | 滑鼠抬起時 |
mousemove | 滑鼠移動時 |
mouseover | 滑鼠移動時 |
mouseout | 滑鼠從元素上離開時 |
mouseup | 滑鼠抬起時 |
mouseenter | 滑鼠移入時觸發,不產生冒泡行為 |
mosueleave | 滑鼠移出時觸發,不產生冒泡行為 |
copy | 複製內容時觸發 |
某一段文字被選中時觸發 | |
scroll | 元素滾動時,可以為元素設定overflow:auto; 產生滾動條來測試 |
事件物件
滑鼠事件產生的事件物件包含相對應的屬性
屬性 | 說明 |
---|---|
which | 執行mousedown/mouseup時,顯示所按的鍵 1左鍵,2中鍵,3右鍵 |
clientX | 相對視窗X座標 |
clientY | 相對視窗Y座標 |
pageX | 相對於文件的X座標 |
pageY | 相對於文件的Y座標 |
offsetX | 目標元素內部的X座標 |
offsetY | 目標元素內部的Y座標 |
altKey | 是否按了alt鍵 |
ctrlKey | 是否按了ctlr鍵 |
shiftKey | 是否按了shift鍵 |
metaKey | 是否按了媒體鍵 |
relatedTarget | mouseover事件時從哪個元素來的,mouseout事件時指要移動到的元素。當無來源(在自身上移動)或移動到視窗外時值為null |
例項操作
禁止複製內容
<body>
abcdefg
</body>
<script>
document.body.addEventListener("copy", event => {
event.preventDefault();
alert("禁止複製");
});
</script>
relatedTarget
是控制滑鼠移動事件的來源和目標物件的
如果移動過快會跳轉中間物件
<body>
<ul>
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
</body>
<script>
document.body.addEventListener("mouseout", () => {
console.log(event.target); // 顯示滑鼠在哪個位置上
console.log(event.relatedTarget);
});
</script>
mouseenter
與mouseleave
不會產生冒泡,即子元素和父元素(當前事件物件)來回移動時不產生事件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
width: 100vw;
height: 100vh;
}
section {
display: flex;
justify-content: center;
align-items: center;
width: 200px;
height: 200px;
border: 1px solid #ddd;
}
article {
width: 100px;
height: 100px;
background-color: darkviolet;
}
</style>
</head>
<body>
<section>
<article></article>
</section>
</body>
<script>
document.querySelector("section").addEventListener("mouseenter", (event) => {
event.target.style.backgroundColor = "red";
});
document.querySelector("article").addEventListener("mouseenter", (event) => {
event.target.style.backgroundColor = "yellow";
});
</script>
</html>
鍵盤事件
事件型別
針對鍵盤輸入操作的行為有多種事件型別
事件名 | 說明 |
---|---|
Keydown | 鍵盤按下時,一直按鍵不鬆開時keydown事件會重複觸發 |
keypress | 某個鍵盤按鍵被按下並且鬆開 |
keyup | 按鍵抬起時 |
事件物件
鍵盤事件產生的事件物件包含相對應的屬性
屬性 | 說明 |
---|---|
keyCode | 返回鍵盤的ASCII字元數字 |
code | 按鍵碼,字元以Key開始,數字以Digit開始,特殊字元有專屬名子。左右ALT鍵字元不同。 不同佈局的鍵盤值會不同 |
key | 按鍵的字元含義表示,大小寫不同。不能區分左右ALT等。不同語言作業系統下值會不同 |
altKey | 是否按了alt鍵 |
ctrlKey | 是否按了ctlr鍵 |
shiftKey | 是否按了shift鍵 |
metaKey | 是否按了媒體鍵 |
表單事件
對於表單元素來說,可有以下事件提供處理
事件型別 | 說明 |
---|---|
focus | 獲取焦點事件 |
blur | 失去焦點事件 |
element.focus() | 讓元素強制獲取焦點 |
element.blur() | 讓元素失去焦點 |
change | 文字框在內容發生改變並失去焦點時觸發,select/checkbox/radio選項改變時觸發事件 |
input | 內容改變時觸發,包括貼上內容或語音輸入內容都會觸發事件 |
submit | 確認按鈕被點選,提交表單時觸發 |