JavaScript Event

雲崖先生發表於2020-08-19

基礎知識

   在文件、瀏覽器、標籤元素等元素在特定狀態下觸發的行為即為事件,比如使用者的單擊行為、表單內容的改變行為即為事件,我們可以為不同的事件定義處理程式。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的指向為windowundefined,但是我們可以通過事件物件提取出事件目標本身

<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>

事件監聽

  使用HTMLDOM繫結都有缺陷,建議使用新的事件監聽繫結方式

方法說明
addEventListener 新增事件處理程式
removeEventListener 移除事件處理程式

  addEventListener

  使用addEventListener新增事件處理程式

  transtionend / DOMContentLoaded 等事件型別只能使用 addEventListener處理

  同一事件型別設定多個事件處理程式,按設定的順序先後執行

  也可以對未來新增的元素繫結事件

  引數說明如下

引數說明
引數1 事件型別
引數2 事件處理程式
引數3 定製選項

  引數3的定製項

   once:true :只執行一次事件

   capture:true/false :捕獲階段傳播到該 EventTarget 時觸發

   passive:truelistener 永遠不會呼叫 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>

注意事項

   通過HTMLDOM進行事件處理程式的繫結,需要在事件名前加入on,比如click就變為onclickcopy變為oncopy

   而使用事件監聽的方式則不需要加上on,這與jQuery的做法如出一轍。

事件物件

  執行事件處理程式時,會產生當前事件相關資訊的物件,即為事件物件。

  系統會自動做為引數傳遞給事件處理程式。

  大部分瀏覽器將事件物件儲存到window.event中

  有些瀏覽器會將事件物件做為事件處理程式的引數傳遞

  事件物件常用屬性如下:

屬性說明
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後執行

  不能執行alertconfirm等互動指令

  發生錯誤也不會阻止頁面關閉或重新整理

<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>

  mouseentermouseleave不會產生冒泡,即子元素和父元素(當前事件物件)來回移動時不產生事件

<!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 確認按鈕被點選,提交表單時觸發

相關文章