事件發生時,你在想什麼?

ZhangCheng發表於2019-02-28

本章內容:

  • 理解事件流
  • 使用事件處理程式
  • 不同的事件型別

事件流

事件流描述的是從頁面中接受事件的順序。但有意思的是,IE和Netscape開發團隊居然提出了差不多完全相反的事件流的概念。IE的事件流是事件冒泡流,而Netscape Communicator的事件流是事件捕獲流。

事件冒泡(event bubbling)

即事件開始時由最具體的元素接收,然後逐級向上傳播到較為不具體的節點。

所有現代瀏覽器都支援事件冒泡,但在具體實現上還是有一些差別。IE5.5及更早版本中的事件冒泡會跳過<html>元素(從<body>直接跳到document)。IE9、Firefox、Chrome和Safari則將事件一致冒泡到window物件。

事件捕獲(event capturing)

事件捕獲的思想是不太具體的節點應該更早接收到事件,而最具體的節點應該最後接收到事件。

DOM事件流

DOM2級事件規定的事件流包括三個階段:事件捕獲階段、處於目標階段和事件冒泡階段。首先發生的是事件捕獲,為截獲事件提供了機會。然後是實際的目標接收到事件。最後一個階段是冒泡階段,可以在這個階段對事件做出響應。
多數支援DOM事件流的瀏覽器都實現了一種特定的行為;即使“DOM2級事件”規範明確要求捕獲階段不會涉及事件目標,但IE9、Safari、Firefox和Opera9.5及更高版本都會在捕獲階段觸發事件物件上的事件。結果,就是有兩個機會在目標物件上面操作事件。

事件處理程式

事件就是使用者或瀏覽器自身執行的某種動作。諸如click、load和mouseover,都是事件的名字。而響應某個事件的函式就是事件處理程式。事件處理程式的名字以“on”開頭,因此click事件的事件處理程式就是onclick,load事件的事件處理程式就是onload。為事件指定處理程式的方式有好幾種。

HTML事件處理程式

<script type=`text/javascript`>
    function showMessage() {
        alert(`hello world`);
    }
</script>
<input type=`button` value=`Click Me` onclick=`showMessage()` />
複製程式碼

在這個例子中,單擊按鈕就會呼叫showMessage()函式。這個函式是在一個獨立的<script>元素中定義的,當然也可以被包含在一個外部檔案中。事件處理程式中的程式碼在執行時,有權訪問全域性作用域中的任何程式碼。

這樣指定事件處理程式具有一些獨到之處。首先,這樣會建立一個封裝著元素屬性值的函式。這個函式中有一個區域性變數event,也就是事件物件:

<input type=`button` value="Click Me" onClick=`alert(event.type)`>
複製程式碼

通過event變數,可以直接訪問事件物件,你不用自己定義它,也不用從函式的引數列表中讀取。在這個函式內部,this值等於事件的目標元素。

<input type=`button` value=`Click Me` onclick=`alert(this.value)`>
複製程式碼

關於這個動態建立的函式,另一個有意思的地方是它擴充套件作用域的方式。在這個函式內部,可以像訪問區域性變數一樣訪問document及該元素本身的成員。這個函式使用with像下面這樣擴充套件作用域:

function () {
    with(document) {
        with(this) {
            // 元素屬性值
        }
    }
}
複製程式碼

如此一來,事件處理程式要訪問自己的屬性就簡單多了。下面這行程式碼與前面的例子效果相同:

<input type=`button` value=`Click Me` onclick=`alert(value)`>
複製程式碼

如果當前元素是一個表單輸入元素,則作用域中還會包含訪問表單元素的入口,這個函式就變成了如下所示:

<form method=`post`>
    <input type=`text` name=`username` value=``>
    <input type=`button` value=`Echo Username` onclick=`alert(username.value)`>
</form>
複製程式碼

在這個例子中,單擊按鈕會顯示文字框中的文字。值得注意的是,這裡直接引用了username元素。
不過,在HTML中指定事件處理程式有兩個缺點。

  • 存在一個執行順序的問題。因為使用者可能會在HTML元素一出現在頁面上就觸發相應的事件,但當時的事件處理程式有可能尚不具備執行條件。以前面的例子來說明。
<input type=`button` value=`Click Me` onclick=`showMessage()` />

<script type=`text/javascript`>
    function showMessage() {
        alert(`hello world`);
    }
</script>
複製程式碼

如果使用者在頁面解析showMessage()函式之前就單擊了按鈕,就會引發錯誤。為此,很多HTML事件處理程式都會被封裝在一個try-catch塊中,以便錯誤不會浮出水面。

<input type=`button` value=`Click Me` onclick=`try{showMessage()}catch(ex){}`>
複製程式碼
  • 另一個缺點就是,這樣擴充套件事件處理程式的作用域鏈在不同瀏覽器中會導致不同結果。不同Javascript引擎遵循的識別符號解析規則略有差異,很可能會在訪問非限定物件成員時出錯。

  • 最後一個缺點是HTML與Javascript程式碼緊密耦合。如果要更換事件處理程式,就要改動兩個地方:HTML程式碼和Javascript程式碼。

DOM0級事件處理程式

通過JavaScript指定事件處理程式的傳統方式,就是講一個函式賦值給一個事件處理程式屬性。這種為事件處理程式賦值的方法是在第四代Web瀏覽器中出現的,而且至今仍然為所有現代瀏覽器所支援。原因:

  • 簡單
  • 跨瀏覽器的優勢

每個元素都有自己的事件處理程式屬性,這些屬性通常全部小寫,例如onclick。將這種屬性的值設定為一個函式,就可以指定事件處理程式:

var btn = document.getElementById(`myBtn`);

btn.onclick = function () {
    alert(`Clicked`);
}
複製程式碼

有一些缺點:
在這些程式碼執行以前不會指定事件處理程式,因此如果這些程式碼在頁面中位於按鈕後面,就有可能在一段時間內怎麼單擊都沒反應。

使用DOM0級方法指定的事件處理程式被認為是元素的方法。因此,這時候的事件處理程式是在元素的作用域中執行;換句話說,程式中的this引用當前元素。

<input type=`button` id=`myBtn`>

var btn = document.getElementById(`myBtn`);

btn.onclick = function () {
    alert(this.id);
}
複製程式碼

單擊按鈕顯示的是元素的ID,這個ID是通過this.id取得的。不僅僅是ID,實際上可以在事件處理程式中通過this訪問元素的任何屬性和方法。以這種方式新增的事件處理程式會在事件流的冒泡階段被處理。
也可以刪除通過DOM0級方法指定的事件處理程式,只要像下面這樣將事件處理程式屬性的值設定為null即可:

btn.onclick = null;
複製程式碼

DOM2級事件處理程式

“DOM2級事件”定義了兩個方法,用於處理指定和刪除事件處理程式的操作:addEventListener()和removeEventListener()。所有DOM節點中都包含這兩個方法,並且它們都接收3個引數:要處理的事件名、作為事件處理程式的函式和一個布林值。最後這個布林值引數如果是true,表示在捕獲階段呼叫事件處理程式;如果是false,表示在冒泡階段呼叫事件處理程式。

要在按鈕上為click事件新增事件處理程式,可以使用下列程式碼:

var btn = document.getElementById(`myBtn`);

btn.addEventListener(`click`, function () {
    alert(this.id);
}, false);
複製程式碼

上面的程式碼為一個按鈕新增了onclick事件處理程式,而且該事件會在冒泡階段被觸發。與DOM0級方法一樣,這裡新增的事件處理程式也是在其依附的元素的作用域中執行。使用DOM2級方法新增事件處理程式的好處是可以新增多個事件處理程式。來看下面的例子。

var btn = document.getElementById(`MyBtn`);

btn.addEventListener(`click`, function () {
    alert(`Hello world`);
}, false);
複製程式碼

通過addEventListener()新增的事件處理程式只能使用removeEventListener()來移除;移除時傳入的引數與新增處理程式時使用的引數相同。這也意味著通過addEventListener()新增的匿名函式將無法移除

var btn = document.getElementById(`myBtn`);

btn.addEventListener(`click`, function () {
    alert(this.id);
}, false)

// 這裡省略了其他程式碼
btn.removeEventListener(`click`, function () {
    alert(this.id);
}, false)  // 這樣無效


var handler = function ()  {
    alert(this.id);
}

btn.addEventListener(`click`, handler, false)


btn.removeEventListener(`click`, handler, false)  // 這樣有效
複製程式碼

大多數情況下,都是將事件處理程式新增到事件流的冒泡階段,這樣可以最大限度地相容各種瀏覽器。最好只在需要在事件到達目標之前截獲它的時候將事件處理程式新增到捕獲階段。如果不是特別需要,我們不建議在事件捕獲階段註冊事件處理程式。

IE事件處理程式(如果放棄IE8及以下可以略過)

IE實現了與DOM中類似的兩個方法:attachEvent()和detachEvent()。這兩個方法接收相同的兩個引數:事件處理程式與事件處理程式函式。由於IE8及更早版本只支援事件冒泡,所以通過attachEvent()新增的事件處理程式都會被新增到冒泡階段。

要使用attachEvent()為按鈕新增一個事件處理程式,可以使用以下程式碼。

var btn = docunment.getElementById(`myBtn`);
btn.attachEvent(`onclick`, function () {
    alert(`Clicked`);
})
複製程式碼

在IE中使用attachEvent()與使用DOM0級方法的主要區別在於事件處理程式的作用域。在使用DOM0級方法的情況下,事件處理程式會在全域性作用域執行,因此this等於window。

來看下面的例子。

var btn = document.getElementById(`myBtn`);
btn.attachEvent(`onclick`, function () {
    alert(this=== window);  // true
})
複製程式碼

在編寫跨瀏覽器的程式碼時,牢記這一區別非常重要。

使用attachEvent()新增的事件可以通過detachEvent()來移除,條件是必須提供相同的引數。與DOM方法一樣,這也意味著新增的匿名函式將不能被移除。不過,只要能夠將對相同函式的引用傳給dtachEvent(),就可以移除相應的事件處理程式。

跨瀏覽器的事件處理程式

為了以跨瀏覽器的方式處理事件,不少開發人員會使用能夠隔離瀏覽器差異的JavaScript庫。

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.detach(`on`+ type, handler);
        } else {
            element[`on` + type] = null;
        }
    }
}

複製程式碼

事件物件

在觸發DOM上的某個事件時,會產生一個事件物件event,這個物件中包含著所有與事件有關的資訊。包括導致事件的元素、事件的型別以及其他與特定事件相關的資訊。

DOM中的事件物件

相容DOM的瀏覽器會將一個event物件傳入到事件處理程式中。無論指定事件處理程式時使用什麼方法(DOM0級或DOM2級),都會傳入event物件。

var btn = document.getElementById(`myBtn`);

btn.onclick = function (event) {
    alert(event.type)  //`click`
}

btn.addEventListener(`click`, function (event) {
    alert(event.type)   //`click`
}, false);
複製程式碼

這個例子中的兩個事件處理程式都會彈出一個警告框,顯示由event.type屬性表示的事件型別。這個屬性始終都會包含被觸發的事件型別。

event物件包含與建立它的特定事件有關的屬性和方法。觸發的事件型別不一樣,可用的屬性和方法也不一樣。不過,所有事件都會有下表列出的成員。

屬性/方法 型別 讀/寫 說明
preventDefault() Function 只讀 取消事件的預設行為。如果cancelable是true,則可以使用這個方法
stopImmediatePropagation() Function 只讀 取消事件的進一步捕獲或冒泡。同時阻止任何事件處理程式被呼叫(DOM3級事件中新增)
stopPropagation() Function 只讀 取消事件的進一步捕獲或冒泡。如果bubbles為true,則可以使用這個方法
target Element 只讀 事件的目標
trusted Boolean 只讀 為true表示事件是瀏覽器生成的。為false表示事件是由開發人員通過Javascript建立的(DOM3級事件中新增)
type String 只讀 被觸發的事件的型別
view AbstractView 只讀 與事件關聯的抽象檢視。等同於發生事件的window物件

看下面例子:

document.body.onclick = function (event) {
    alert(event.currentTarget === document.body);  // true
    alert(this === document.body) // true
    alert(event.target === document.getElementById(`myBtn`)); // true
};
複製程式碼

當單擊這個例子中的按鈕時,this和currentTarget都等於document.body,因為事件處理程式是註冊到這個元素上的。然而,target元素卻等於按鈕元素,因為它是click事件真正的目標。由於按鈕上並沒有註冊事件處理程式,結果click事件就冒泡到document.body,在那裡事件才得到了處理。

在需要通過一個函式處理多個事件時,可以使用type屬性。例如:

var btn = document.getElementById(`myBtn`);

var handler = function (event) {
    switch (event.type) {
        case `click`:
            alert(`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;
複製程式碼

要阻止特定事件的預設行為,可以使用preventDefault()方法。例如,連結的預設行為就是在被單擊時會導航到其href特性指定的URL。如果你想阻止連結導航這一預設行為,那麼通過連結onclick事件處理程式可以取消它。

只有cancelable屬性設定為true的事件,才可以使用preventDefault()來取消其預設行為。

另外,stopPropagation()方法用於立即停止事件在DOM層次中的傳播,即取消進一步的事件捕獲或冒泡。例如,直接新增到一個按鈕的事件處理程式可以呼叫stopPropagation(),從而避免觸發註冊在document.body上面的事件處理程式,如下面的例子所示。

事件物件的eventPhase屬性,可以用來確定事件當前正位於事件流的哪個階段。如果是在捕獲階段呼叫的事件處理程式,eventPhase等於1;如果事件處理程式處於目標物件上,則eventPhase等於2;如果在冒泡階段呼叫的事件處理程式,eventPhase等於3。
注意,儘管“處於目標”發生在冒泡階段,但eventPhase仍然一直等於2。

當eventPhase等於2時,this、target和currentTarget始終是相等的。

只有在事件處理程式執行期間,event物件才會存在;一旦事件處理程式執行完成,event物件就會被銷燬。

IE中的事件物件

與訪問DOM中的event物件不同,要訪問IE中的event物件有幾種不同的方式,取決於指定事件處理程式的方法。在使用DOM0級方法新增事件處理程式時,event物件作為window物件的一個屬性存在。來看下面的例子。

var btn = document.getElementById(`myBtn`);
btn.onclick = function () {
    var event = window.event;
    alert(event.type)  //`click`
}
複製程式碼

可是,如果事件處理程式是使用attachEvent()新增的,那麼就會有一個event物件作為引數被傳入事件處理程式函式中,如下所示。

var btn = document.getElementById(`myBtn`);
btn.attacehEvent(`onclick`, function (event) {
    alert(event.type);  //`click`
})
複製程式碼

在像這樣使用attachEvent()情況下,也可以通過window物件來訪問event物件,就像使用DOM0級方法時一樣。不過為方便起見,同一個物件也會作為引數傳遞。

如果是通過HTML特性指定的事件處理程式,那麼還可以通過一個名叫event的變數來訪問event物件(與DOM中的事件模型相同)。

<input type=`button` value=`Click Me` onclick=`alert(event.type)` >
複製程式碼

IE的event物件同樣也包含與建立它的事件相關的屬性和方法。其中很多屬性和方法都有對應的或者相關的DOM屬性和方法。與DOM的event物件一樣,這些屬性和方法也會因為事件型別的不同而不同,但所有事件物件都會包含下表所列的屬性和方法。

屬性/方法 型別 讀/寫 說明
cancelBubble Boolean 讀/寫 預設值false,但將其設定為true就可以取消事件冒泡(與DOM中的stopPropagation()方法的作用相同)
returnValue Boolean 讀/寫 預設值為true,將其設定為false就可以取消事件的預設行為(與DOM中的preventDefault()方法的作用相同)
srcElement Element 只讀 事件的目標(與DOM中的target屬性相同)
type String 只讀 被觸發的事件的型別

因為事件處理程式的作用域是根據指定它的方式來確定的,所以不能認為this會始終等於事件目標。故而,最好還是使用event.srcElement比較保險。

跨瀏覽器的事件物件

雖然DOM和IE中的event物件不同,但基於它們之間的相似性依舊可以拿出跨瀏覽器的方案來。IE中event物件的全部資訊和方法DOM物件中都有,只不過實現方式不一樣。不過,這種對應關係讓實現兩種事件模型之間的對映非常容易。可以對前面介紹的EventUtil物件加以增強,新增如下方法以求同存異。

var EventUtil = {
    addHandler: function (element, type, handler) {},
    removeHandler: 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;
        }
    },
    stopPropagation: function (event) {
        if (event.stopPropagation) {
            event.stopPropagation();
        } else {
            event.cancelBubble = true;
        }
    }
}

複製程式碼

跨瀏覽器阻止事件冒泡:

var btn = document.getElementById(`myBtn`);

btn.onclick = function (event) {
    alert(`Clicked`);
    
    event = EventUtil.getEvent(event);
    EventUtil.stopPropagation(event);
}

document.body.onclick = function (event) {
    alert(`Body clicked`);
}
複製程式碼

事件型別

Web瀏覽器中可能發生的事件有很多型別。

“DOM3級事件”規定了以下幾類事件。

  • UI事件,當使用者與頁面上的元素互動時觸發。
  • 焦點事件,當元素獲得或失去焦點時觸發。
  • 滑鼠事件,當使用者通過滑鼠在頁面上執行操作時觸發。
  • 滾輪事件
  • 文字事件
  • 鍵盤事件
  • 合成事件,**當為IME(Input Method Editor)輸入字元時觸發
  • 變動(mutation)事件,當底層DOM結構發生變化時觸發
  • 變動名稱事件,當元素或屬性名變動時觸發。此類事件已經被廢棄,沒有任何瀏覽器實現它們

UI事件

UI事件指的是那些不一定與使用者操作有關的事件。這些事件在DOM規範出現之前,都是以這個或那種形式存在的,而在DOM規範中保留是為了向後相容。
現有的UI事件如下:

  • DOMActivate:表示元素已經被使用者操作啟用。
  • load:當頁面完全載入後在window上面觸發,當所有框架都載入完畢時在框架集上面觸發,當影像載入完畢時在事件發生時,你在想什麼?元素上觸發,或者當嵌入的內容載入完畢時在元素上面觸發。
  • unload:當頁面完全解除安裝後在window上面觸發,當所有框架都解除安裝後在框架集上面觸發,或者當嵌入的內容解除安裝完畢後在元素上面觸發。
  • abort:在使用者停止下載過程時,如果嵌入的內容沒有載入完,則在元素上面觸發。
  • error:當發生Javascript錯誤時在window上面觸發,當無法載入影像時在事件發生時,你在想什麼?元素上面觸發,當無法載入嵌入內容時在元素上面觸發,或者當有一或多個框架無法載入時在框架集上面觸發。
  • select:當使用者選擇文字框(<input>或<textarea>)中的一或多個字元時觸發。
  • resize:當視窗或框架的大小變化時在window或框架上面觸發。
  • scroll:當使用者滾動帶滾動條的元素中的內容時,在該元素上面觸發。元素中包含所載入頁面的滾動條。
  • 多數這些事件都與window物件或表單控制元件相關。

    要確定瀏覽器是否支援DOM2級事件規定的HTML事件,可以使用如下程式碼:

var isSupported = document.implementation.hasFeature(`HTMLEvents`, `2.0`);
複製程式碼

load事件

最常用的一個事件。當頁面完全載入後(包括所有影像、JavaScript檔案、CSS檔案等外部資源),就會觸發window上面的load事件。

有兩種定義onload事件處理程式的方式。
第一種方式是使用如下所示的JavaScript程式碼:

EventUtil.addHandler(window, `load`, function (event) {
    alert(`Loaded!`);
})
複製程式碼

第二種指定onload事件處理程式的方式是為<body>元素新增一個onload特性:

<!DOCTYPE html>
<html>
<head>
    <title>Load Event Example</title>
</head>
<body onload=`alert("loaded!")`>
</body>
</html>
複製程式碼

一般來說,在window上面發生的任何事件都可以在<body/>元素中通過相應的特性來指定,因為在HTML中無法訪問window元素。實際上,這只是為了保證向後相容的一種權宜之計。建議儘可能使用javascript方式。

影像上面也可以觸發load事件,無論是在DOM中的影像元素還是HTML中的影像元素。因此,可以在HTML中為任何影像指定onload事件處理程式:

<img src=`smile.gif` onload=`alert("Image loaded.")`>
複製程式碼

這樣,當例子中的影像載入完畢後就會顯示一個警告框。同樣的功能可以使用JavaScript實現:

var $image = document.getElementById(`myImg`);
$image.addEventListener(`load`, function (e) {
    alert(`image loaded`);
})
複製程式碼

還有一些元素也以非標準的方式支援load事件。在IE9+、Firefox、Opera、Chrome和Safari 3+及更高版本中,<script>元素也會觸發load事件,以便開發人員確定動態載入的JavaScript檔案是否載入完畢。與影像不同,只有設定了<script>元素的src屬性並將元素新增到文件後,才會開始下載JavaScript檔案。換句話說,對於<script>元素而言,指定src屬性和指定事件處理程式的先後順序就不重要了。

unload事件

與load事件對應的是unload事件,這個事件在文件被完全解除安裝後觸發。只要使用者從一個頁面切換到另一個頁面,就會發生unload事件。而利用這個事件最懂的情況是清除引用,以避免記憶體洩漏。

**無論使用那種方式,都要小心編寫onunload事件處理程式中的程式碼。既然unload事件是在一切都被解除安裝之後才觸發,那麼在頁面載入後存在的那些物件,此時就不一定存在了。此時,操作DOM節點或者元素的樣式就會導致錯誤。

resize事件

當瀏覽器視窗被調整到一個新的高度或寬度時,就會觸發resize事件。

關於何時會觸發resize事件,不同瀏覽器有不同的機制。IE、Safari、Chrome和Opera會在瀏覽器視窗變化了1畫素就觸發resize事件,然後隨著變化不斷重複觸發。Firefox則只會在使用者停止調整視窗大小時才會觸發resize事件。

scroll事件

雖然scroll事件是在window物件上發生的,但它實際表示的則是頁面中相應元素的變化。
**在混雜模式下,可以通過<body>元素的scrollLeft和scrollTop來監控到這一變化;而在標準模式下,除Safari之外的所有瀏覽器都會通過<html>元素來反映這一變化(Safari仍然基於<body>跟蹤滾動位置)。 **

與resize事件類似,scroll事件也會在文件被滾動期間重複被觸發,所以有必要儘量保持事件處理程式的程式碼簡單。

焦點事件

焦點事件會在頁面獲得或失去焦點時觸發。利用這些事件並與document.hasFocus()方法及document.activeElement屬性配合,可以知曉使用者在頁面上的行蹤,有以下6個焦點事件。

  • blur:在元素失去焦點時觸發。這個事件不會冒泡;所有瀏覽器都支援它。
  • DOMFocusIn: 只有opera支援
  • DOMFocusOut: 只有opera支援
  • focus:在元素獲得焦點時觸發,不會冒泡
  • focusin:在元素獲得焦點時觸發,與focus等價,但是會冒泡
  • focusout: 在元素失去焦點時觸發

這一類事件中最主要的兩個是focus和blur,它們都是javscript早期就得到所有瀏覽器支援的事件。這些事件的最大問題是不冒泡。因此,IE的focusin和focusout與Opera的DOMFocusIn和DOMFocusOut才會重疊。IE的方式最後被DOM3級事件採納為標準方式。

當焦點從頁面中的一個元素移動到另一個元素,會依次觸發下列事件:

  • focusout在失去焦點的元素上觸發
  • focusin在獲得焦點的元素上觸發
  • blur在失去焦點的元素上觸發
  • DOMFocusOut在失去焦點的元素上觸發
  • focus在獲得焦點的元素上觸發
  • DOMFocusIn在獲得焦點的元素上觸發
    其中,blur、DOMFocusOut和focusout的事件目標是失去焦點的元素;而focus、DOMFocusIn和focusin的事件目標是獲得焦點的元素。

即使focus和blur不冒泡,也可以在捕獲階段偵聽它們。

下面演示全域性偵聽blur和focus

滑鼠與滾輪事件

滑鼠事件是Web開發中最常用的一類事件。DOM3級事件中定義了9個滑鼠事件。

  • click 略
  • dblclick 略
  • mousedown 略
  • mouseenter,在滑鼠游標從元素外部首次移動到元素範圍之內時觸發。這個事件不冒泡,而且在游標移動到後代元素上不會觸發。DOM2級事件並沒有定義這個事件,但DOM3級事件將它納入了規範。IE、Firefox 9+和Opera支援這個事件。
  • mouseleave,在位於元素上方的滑鼠游標移動到元素範圍之外時觸發。這個事件不冒泡,而且在游標移動到後代元素上不會觸發。DOM2級事件並沒有定義這個事件,但DOM3級事件將它納入了規範。IE、Firefox 9+和Opera支援這個事件。
  • mousemove,當滑鼠指標在元素內部移動時重複地觸發。不能通過鍵盤觸發這個事件。
  • mouseout,在滑鼠指標位於一個元素上方,然後使用者將其移入另一個元素時觸發。又移入的另一個元素可能位於前一個元素的外部,也可能是這個元素的子元素。不能通過鍵盤觸發這個事件。
  • mouseover,在滑鼠指標位於一個元素外部,然後使用者將其首次移入另一個元素邊界之內時觸發。
  • mouseup,在使用者釋放滑鼠按鈕時觸發。不能通過鍵盤觸發這個事件。

頁面中的所有元素都支援滑鼠事件。除了mouseenter和mouseleave,所有滑鼠事件都會冒泡,也可以被取消,而取消滑鼠事件將會影響瀏覽器的預設行為。取消滑鼠事件的預設行為還會影響其他事件,因為滑鼠事件與其他事件是密不可分的關係。

**只有在同一個元素上相繼觸發mousedown和mouseup事件,才會觸發click事件;如果mousedown或mouseup中的一個被取消,就不會觸發click事件。類似地,只有觸發兩次click事件,才會觸發一次dblclick事件。如果有程式碼阻止了連續兩次觸發click事件,那麼就不會觸發dblclick事件了。

這4個事件觸發的順序始終如下:

  • mousedown
  • mouseup
  • click
  • mousedown
  • mouseup
  • click
  • dblclick

使用以下程式碼可以檢測瀏覽器是否支援以上DOM2級事件(除dbclick、mouseenter和mouseleave之外):

var isSupported = document.implementation.hasFeature(`MouseEvents`, `2.0`);
複製程式碼

要檢測瀏覽器是否支援上面的所有事件,可以使用以下程式碼:

var isSupported = document.implementation.hasFeature(`MouseEvents`, `3.0`);
複製程式碼

滑鼠事件中還有一類滾輪事件。而說是一類事件,其實就是一個mousewheel事件。這個事件跟蹤滑鼠滾輪,類似於Mac的觸控板。

客戶區座標

滑鼠事件都是在瀏覽器視口中特定的位置上發生的。這個位置資訊儲存在事件物件的clientX和clientY屬性中。所有瀏覽器都支援這兩個屬性。

頁面座標位置

通過客戶區座標能夠知道滑鼠是在視口中什麼位置發生的,而頁面座標通過事件物件的pageX和pageY屬性,能告訴你事件是在頁面中的什麼位置發生的。換句話說,這兩個屬性表示滑鼠游標在頁面中的位置,因此座標是從頁面本身而非視口的左邊和頂邊計算的。

螢幕座標位置

滑鼠事件發生時,不僅會有相對於瀏覽器視窗的位置,還有一個相對於整個電腦螢幕的位置。而通過screenX和screenY屬性就可以確定滑鼠事件發生時滑鼠指標相對於整個螢幕的座標資訊。

修改鍵

雖然滑鼠事件主要是使用滑鼠來觸發的,但在按下了滑鼠時鍵盤上的某些鍵的狀態也可以影響到所要採取的操作。這些修改鍵就是Shift、Ctrl、Alt和Meta,它們經常被用來修改滑鼠事件的行為。DOM為此規定了4個屬性,表示這些修改鍵的狀態:shiftKey、ctrlKey、altKey和metaKey。

相關元素

在發生mouseover和mouseout事件時,還會涉及更多的元素。這兩個事件都涉及把滑鼠指標從一個元素的邊界之內移動到另一個元素的邊界之內。對mouseover事件而言,事件的主目標是獲得游標的元素,而相關元素就是那個失去游標的元素。類似的,對mouseout事件而言,事件的主目標是失去游標的元素,而相關元素則是獲得游標的元素。

DOM通過event物件的relatdTarget屬性提供了相關元素的資訊。這個屬性只對於mouseover和mouseout事件才包含值;對於其他事件,這個屬性的值是null。IE8及之前版本不支援relatedTarget屬性,但提供了儲存著同樣資訊的不同屬性。在mouseover事件觸發時,IE的fromElement屬性中儲存了相關元素;在mouseout事件觸發時,IE的toElement屬性中儲存著相關元素。

滑鼠按鈕

只有在主滑鼠按鈕被單擊時才會觸發click事件,因此檢測按鈕的資訊並不是必要的。但對於mousedown和mouseup事件來說,則在其event物件存在一個button屬性,表示按下或釋放的按鈕。DOM的button屬性可能有如下3個值:0表示主滑鼠按鈕,1表示中間的滑鼠按鈕,2表示次滑鼠按鈕。在常規的設定中,主滑鼠按鈕就是滑鼠左肩,而次滑鼠按鈕就是滑鼠右鍵。

在使用onmouseup事件處理程式時,button的值表示釋放的是哪個按鈕。此外,如果不是按下或釋放了主滑鼠按鈕,Opera不會觸發mouseup或mousedown事件。

更多的事件資訊

“DOM2級事件”規範在event物件中還提供了detail屬性,用於給出有關事件的更多資訊。對於滑鼠事件來說,detail中包含了一個數值,表示在給定位置上發生了多少次單擊。在同一個畫素上相繼地發生一次mousedown和一次mouseup事件算作一次單擊。detail屬性從1開始計數,每次單擊發生後都會遞增。如果滑鼠在mousedown和mouseup之間移動了位置,則detail會被重置為0

滑鼠滾輪事件

IE6.0首先實現了mousewheel事件。此後,Opera、Chrome和Safari也都實現了這個事件。當使用者通過滑鼠滾輪與頁面互動、在垂直方向上滾動頁面時,就會觸發mousewheel事件。這個事件可以在任何元素上面觸發,最終會冒泡到document或window物件。與mousewheel事件對應的event物件除包含滑鼠事件的所有標準資訊外,還包含一個特殊的wheelDelta屬性。當使用者向前滾動滑鼠滾輪時,wheelDelta是120的倍數;當使用者向後滾動滑鼠滾輪時,wheelDelta是-120倍數。

觸控裝置

IOS和android裝置的實現非常特別,因為這些裝置沒有滑鼠。在面向iPhone和iPod中的Safari開發時,要記住以下幾點:

  • 不支援dblclick事件。雙擊瀏覽器視窗會放大畫面,而且沒有辦法改變該行為
  • 輕擊可單擊元素會觸發mousemove事件。如果此操作會導致內容變化,將不再有其他事件發生;如果螢幕沒有因此變化,那麼會依次發生mousedown、mouseup和click事件。輕擊不可單擊的元素不會觸發任何事件。可單擊的元素是指那些單擊可產生預設操作的元素(如連結),或者那些已經被指定了onclick事件處理程式的元素。
  • 兩個手指放在螢幕上且頁面隨手指移動而滾動時會觸發mousewheel和scroll事件。

鍵盤與文字事件

使用者在使用鍵盤時會觸發鍵盤事件。“DOM2級事件”最初規定了鍵盤事件,但在最終定稿之前又刪除了相應的內容。結果,對鍵盤事件的支援主要遵循的是DOM0級。

“DOM3級事件”為鍵盤事件指定了規範,IE9率先完全實現了該規範。其他瀏覽器也在著手實現這一標準,但仍然有很多問題。

  • keydown:當使用者按下鍵盤上的任意鍵時觸發,而且如果按住不放的話,會重複觸發。
  • keypress: 當使用者按下鍵盤上的字元鍵時觸發,而且如果按住不放的話,會重複觸發此事件。
  • keyup: 當使用者釋放鍵盤上的鍵時觸發。
    雖然所有元素都支援以上3個事件,但只有在使用者通過文字框輸入文字時才最常用到。

只有一個文字事件:textInput。這個事件是對keypress的補充,用意是在將文字顯示給使用者之前更容易攔截文字。在文字插入文字框之前會觸發textInput事件。

在使用者按了一下鍵盤上的字元鍵時,首先會觸發keydown事件,然後緊跟著是keypress事件,最後會觸發keyup事件。其中,keydown和keypress都是在文字框發生變化之前被觸發的;而keyup事件則是在文字框已經發生變化之後被觸發的。如果使用者按下了一個字元鍵不放,就會重複觸發keydown和keypress事件,直到使用者鬆開該鍵為止。

如果使用者按下的是一個非字元鍵,那麼首先會觸發keydown事件,然後是keyup事件。如果按著不放,就會一直重複觸發keydown事件,直到使用者鬆開這個鍵,此時會觸發keyup事件。

鍵盤事件與滑鼠事件一樣,都支援相同的修改鍵。而且,鍵盤事件的事件物件中也有shiftKey、ctrlKey、altKey和metaKey屬性。IE不支援metaKey。

鍵碼

在發生keydown和keyup事件時,event物件的keyCode屬性中會包含一個程式碼,與鍵盤上一個特定的鍵對應。對數字字母字元鍵,keyCode屬性的值與ASCII碼中對應下洩字母或數字的編碼相同。

因此,數字鍵7的keyCode值為55,而字母A鍵的keyCode值為65 ———— 與Shift鍵的狀態無關。

字元編碼

發生keypress事件意味著按下的鍵會影響到螢幕中文字的顯示。在所有瀏覽器中,按下能夠插入或刪除字元的鍵都會觸發keypress事件;按下其他鍵能否觸發此事件因瀏覽器而異。

DOM3級變化

儘管所有瀏覽器都實現了某種形式的鍵盤事件,DOM3級事件還是做出了一些改變。比如,DOM3級事件中的鍵盤事件,不再包含charCode屬性,而是包含兩個新屬性:key和char。

其中,key屬性是為了取代keyCode而新增的,它的值是一個字串。在按下某個字元鍵時,key的值就是相應的文字字元(如k或M);在按下非字元鍵時,key的值是相應鍵的名(如Shift或Down)。而char屬性在按下字元鍵時的行為與key相同,但在按下非字元鍵時值為null。
DOM3級事件還新增了一個名為location的屬性,這是一個數值,表示按下了什麼位置上的鍵:

  • 0表示預設鍵盤
  • 1表示左側位置(如左位的Alt鍵)
  • 2表示右側位置(右側的Shift鍵)
  • 3表示數字小鍵盤
  • 4表示移動裝置鍵盤(也就是虛擬鍵盤)
  • 5表示手柄

textInput事件

“DOM3級事件”規範中引入了一個新事件,名叫textInput。根據規範,當使用者在可編輯區域中輸入字元時,就會觸發這個事件。這個用於替代keypress的textInput事件的行為稍有不同。

  • 區別之一就是任何可以獲得焦點的元素都可以觸發keypress事件,但只有可編輯區域才能觸發textInput事件。
  • 區別之二是textInput事件只會在使用者按下能夠輸入實際字元的鍵時才會被觸發,而keypress事件則在按下那些能夠影響文字顯示的鍵時也會觸發(如退格鍵)。

由於textInput事件主要考慮的是字元,因此它的event物件中包含一個data屬性,這個屬性的值就是使用者輸入的字元(而非字元編碼)。換句話說,使用者在沒有按上檔鍵的情況下按下了S鍵,data的值就是‘s`,而如果在按住上檔鍵時按下該鍵,data的值就是`S`。

裝置中的鍵盤事件

任天堂Wii會在使用者按下Wii遙控器上的按鍵時觸發鍵盤事件。

複合事件(composition event)

複合事件是DOM3級事件中新新增的一類事件,用於處理IME的輸入序列。
IME(Input Method Editor,輸入法編輯器)可以讓使用者輸入在物理鍵盤上找不到的字元。例如,使用拉丁文鍵盤的使用者通過IME照樣能輸入日文字元。IME通常需要同時按住多個鍵,但最終只輸入一個字元。複合事件就是針對檢測和處理這種輸入而設計的。

  • compositionstart: 在IME的輸入法開啟時觸發,表示要開始輸入了。
  • compositionupdate: 在向輸入欄位中插入新字元時觸發。
  • compositionend:在IME的輸入法關閉時觸發,表示返回正常鍵盤輸入狀態。

複合事件與文字事件有很多方面都很相似。在觸發複合事件時,目標是接收文字的輸入欄位。但它比文字事件的事件物件多一個屬性data,其中包含以下幾個值中的一個:

  • 如果在compositionstart事件發生訪問,包含正在編輯的文字。
  • 如果在compositionupdate事件發生時訪問,包含正插入的新字元。
  • 如果在compositionend事件發生時訪問,包含此次輸入會話中插入的所有字元。

利用監聽compositionstart判斷是否開啟了輸入法。從而實現體驗較為良好相容性較強的監控字數的控制元件。

變動事件

DOM2級的變動(mutation)事件能在DOM中的某一部分發生變化時給出提示。變動事件是為XML或HTML DOM設計的,並不特定於某種語言。DOM2級定義瞭如下變動事件。

  • DOMSubtreeModified:在DOM結構中發生任何變化時觸發。這個事件在其他任何事件觸發後都會觸發
  • DOMNodeInserted:在一個節點作為子節點被插入到另一個節點中時觸發。
  • DOMNodeRemoved:在節點從其父節點中被移除時觸發。
  • DOMNodeInertedIntoDocument:在一個節點被直接插入文件或通過子樹間接插入文件之後觸發。這個事件在DOMNodeInserted之後觸發。
  • DOMNodeRemovedFromDocument:在一個節點被直接從文件中移除或通過子樹間接從文件中移除之前觸發。
  • DOMAttrModified:在特性被修改之後觸發。
  • DOMCharacterDataModified:在文字節點的值發生變化時觸發。

使用下列程式碼可以檢測出瀏覽器是否支援變動事件:

var isSupported = document.implementation.hasFeature(`MutationEvents`, `2.0`);
複製程式碼

刪除節點時的觸發順序

  • 在使用removeChild()或replaceChild()從DOM中刪除節點時,首先會觸發DOMNodeRemoved事件。這個事件的目標(event.target)是被刪除的節點,而event.relatedNode屬性中包含著對目標節點父節點的引用。在這個事件觸發時,節點尚未從其父節點刪除,因此其parentNode屬性仍然指向父節點。

  • 如果被移除的節點包含子節點,那麼在其所有子節點以及這個被移除的節點上會相繼觸發DOMNodeRemovedFromDocument事件。但這個事件不會冒泡,所以只有直接指定給其中一個子節點的事件處理程式才會被呼叫。這個事件的目標是相應的子節點或者那個被移除的節點,除此之外event物件中不包含其他資訊。

  • 緊隨其後觸發的是DOMSubtreeModified事件。這個事件的目標是被移除節點的父節點;此時的event物件也不會提供與事件相關的其他資訊。

插入節點時的觸發順序

  • 在使用appendChild()、replaceChild()或insertBefore()向DOM中插入節點時,首先會觸發DOMNodeInserted事件。這個事件的目標是被插入的節點,而event.relatedNode屬性中包含一個對父節點的引用。在這個事件觸發時,節點已經被插入到了新的父節點中。這個事件是冒泡的,因此可以在DOM的各個層次上處理它。
  • 緊接著,會在新插入的節點上面觸發DOMNodeInsertedIntoDocument事件。這個事件不冒泡,因此必須在插入節點之前為它新增這個事件處理程式。這個事件的目標是被插入的節點,除此之外event物件中不包含其他資訊。
  • 最後一個觸發的事件是DOMSubtreeModified,觸發於新插入節點的父節點。

HTML5事件

DOM規範沒有涵蓋所有瀏覽器支援的所有事件。HTML5詳盡列出了瀏覽器應該支援的所有事件。本節只討論其中得到瀏覽器完善支援的事件,但並非全部事件。

contextmenu事件

contextmenu這個事件,用以表示何時應該顯示上下文選單,以便開發人員取消預設的上下文選單而提供自定義選單。

由於contextmenu事件是冒泡的,因此可以為document指定一個事件處理程式,用以處理頁面中發生的所有此類事件。這個事件的目標是發生使用者操作的元素。在所有瀏覽器中都可以取消這個事件。

這個事件的目標是發生使用者操作的元素。在所有瀏覽器中都可以取消這個事件:在相容DOM的瀏覽器中,使用event.preventDefault();在IE中,將event.returnValue()的值,設定為false。因為contextmenu事件屬於滑鼠事件,所以其事件物件中包含與游標位置有關的所有屬性。通常使用contextmenu事件來顯示自定義的上下文選單,而使用onclick事件處理程式來隱藏該選單。

beforeunload事件

之所以發生在window物件上的beforeunload事件,是為了讓開發人員有可能在頁面解除安裝前阻止這一操作。這個事件會在瀏覽器解除安裝頁面之前觸發,可以通過它來取消解除安裝並繼續使用原有頁面。但是,不能徹底取消這個事件,因為那就相當於讓使用者無法離開當前頁面了。為此,這個事件的意圖是將控制權交給使用者。

DOMContentLoaded事件

window的load事件會在頁面中的一切都載入完畢時觸發,但這個過程可能會因為要載入的外部資源過多而破費周折。而DOMContentLoaded事件則在形成完整的DOM樹之後就會觸發,不理會影像、JavaScript檔案、CSS檔案或其他資源是否已經下載完畢。

DOMContentLoaded事件物件不會提供任何額外的資訊(其target屬性是document)。

**對於不支援DOMContentLoaded的瀏覽器,我們建議在頁面載入期間設定一個時間為0毫秒的超時呼叫:

setTimeout(function () {
    // 在此新增事件處理程式
}, 0);
複製程式碼

這個程式碼的實際意思就是:”在當前JavaScript處理完成後立即執行這個函式。”在頁面下載和構建期間,只有一個JavaScript處理過程,因此超時呼叫會在該過程結束時立即觸發。為了確保這個方法有效,必須將其作為頁面中的第一個超時呼叫;即便如此,也還是無法保證在所有環境中該超時呼叫一定會早於load事件被觸發。

readystatechange事件

IE為DOM文件中的某些部分提供了readystatechange事件。這個事件的目的是提供與文件或元素的載入狀態有關的資訊。但這個事件的行為有時候也很難預料。支援readystatechange事件的每個物件都有一個readystate屬性,可能有下列5個值中的一個:

  • uninitialized 未初始化: 物件存在但尚未初始化
  • loading 正在載入: 物件正在載入資料
  • loaded 載入完畢: 物件載入資料完成
  • interactive 互動: 可以操作物件了,但還沒有完全載入
  • complete 完成: 物件已經載入完畢了

這些狀態看起來很直觀,但並非所有物件都會經歷readyState的這幾個階段。換句話說,如果某個階段不適用某個物件,則該物件完全可能跳過該階段;並沒有規定哪個階段適用於哪個物件。顯然,這意味著readystatechange事件經常會少於4次,而readyState屬性的值也不總是連續。

對於document而言,值為“interactive”的readyState會在與DOMContentLoaded大致相同的時刻觸發readystatechange事件。此時,DOM樹已經載入完畢,可以安全地操作它了,因此就會進入互動interactive階段。

這個事件的event物件不會提供任何資訊,也沒有目標物件。

在與load事件一起使用時,無法預測兩個事件觸發的先後順序。在包含較多或較大的外部資源的頁面中,會在load事件觸發之前先進入互動階段;而在包含較少或較小的外部資源的頁面中,則很難說readystatechange事件會發生在load事件前面。

讓問題變得更復雜的是,互動階段可能會早於也可能會晚於完成階段出現,無法確保順序。在包含較多外部資源的頁面中,互動階段更有可能早於完成階段出現;而在頁面中包含較少外部資源的情況下,完成階段先於互動階段出現的可能性更大。因此,為了儘可能搶到先機,有必要同時檢測互動和完成階段。

var handler =  function (e) {
    if (document.readyState == `interactive` || document.readyState == `complete`) {
        document.removeEventListener(`readystatechange`, handler);
        alert(`Content loaded`);
    }
document.addEventListener(`readystatechange`, handler);
複製程式碼

pageshow和pagehide事件

Firefox和Opera有一個特性,名叫“往返快取”(back-forward cache),可以在使用者使用瀏覽器的“後退”和“前進”按鈕時加快頁面的轉換速度。這個快取中不僅儲存著頁面資料,還儲存了DOM和JavaScript狀態;實際上是將整個頁面都儲存在了記憶體裡。如果頁面位於bfcache中,那麼再次開啟該頁面時就不會觸發load事件。儘管由於記憶體中儲存了整個頁面的狀態,不觸發load事件也不應該會導致什麼問題,但為了更形象地說明bfcache行為,Firefox還是提供了一些新事件。

第一個事件就是pageshow,這個事件在頁面顯示時觸發,無論該頁面是否來自bgcache。在重新載入的頁面中,pageshow會在load事件觸發後觸發;而對於bfcache中的頁面,pageshow會在頁面狀態完全恢復的那一刻觸發。

另外值得注意的是,雖然這個事件的目標是document,但必須將其事件處理程式新增到window。

(function () {
    var pageshowCount = 0;

    window.addEventListener(`pageshow`, function (e) {
        pageshowCount++;
        notifyMe(`page show: ` + pageshowCount);
    })
})()
複製程式碼

這個例子使用了私有作用域,以防止變數showCount進入全域性作用域。當頁面首次載入完成時,showCount的值為0。此後,每當觸發pageshow事件,showCount的值就會遞增並通過警告框顯示出來。如果你在離開包含以上程式碼的頁面之後,又單擊“後退”按鈕返回該頁面,就會看到showCount每次遞增的值。這是因為該變數的狀態,乃至整個頁面的狀態,都被儲存在了記憶體中,當你返回這個頁面時,它們的狀態得到了恢復。如果你單擊了瀏覽器的“重新整理”按鈕,那麼showCount的值就會被重置為0,因為頁面已經完全重新載入了。

除了通常的屬性之外,pageshow事件的event物件還包含了一個名為presisted的布林值屬性。如果頁面被儲存在了bfcache中,則這個屬性的值為true;否則,這個屬性的值為false。

通過檢測persisted屬性,就可以根據頁面在bfcache中的狀態來確定是否需要採取其他操作。
與pageshow事件對於的是pagehide事件,該事件會在瀏覽器解除安裝頁面的時候觸發,而且是在unload事件之前觸發。與pageshow事件一樣,pagehide在document上面觸發,但其事件處理程式必須要新增到window物件。這個事件的event物件也包含persisted屬性,不過其用途稍有不同。

hashchange事件

HTML5新增了hashchange事件,以便在URL的引數列表發生變化時通知開發人員。之所以新增這個事件,是因為在Ajax應用中,開發人員經常要利用URL引數列來儲存狀態或導航資訊。

**使用以下程式碼可以檢測瀏覽器是否支援hashchange事件:

var isSupported = (`onhashchange` in window);
複製程式碼

裝置事件

智慧手機和平板電腦的普及,為使用者與瀏覽器互動引入了一種新的方式,而一類新事件也應運而生。device event可以讓開發人員確定使用者在怎樣使用裝置。

orientationonchange事件

只要使用者改變了裝置的檢視模式,就會觸發orientationchange事件。此時的event物件不包含任何有價值的資訊,因為唯一相關的資訊可以通過window。orientation訪問到。

所有IOS裝置都支援orientationchange事件和window。orientation屬性。

MozOrientation事件(已廢棄)

firefox提供。這個事件與ios中的orientationchange事件不同,該事件只能提供一個平面的方向變化。由於MozOrientation事件是在window物件上觸發的,所以可以使用以下程式碼來處理。

deviceorientation事件

本質上,DeviceOrientation Event規範定義的deviceorientation事件與MozOrientation事件類似。它也是在加速計檢測到裝置方向變化時在window物件上觸發,而且具有與MozOrientation事件相同的支援限制。
觸發deviceorientation事件時,事件物件中包含著每一軸相對於裝置靜止狀態下發生變化的資訊。事件物件包含以下5個屬性。

  • alpha
  • beta
  • gamma
  • absolute
  • compassCalibrated

devicemotion事件

devicemotion事件。這個事件是要告訴開發人員裝置什麼時候移動,而不僅僅是裝置方向如何改變。
事件物件包含以下屬性:

  • acceleration: 一個包含x、y和z屬性的物件,在不考慮重力的情況下,告訴你在每個方向上的加速度。
  • accelerationIncludingGravity:一個包含x、y和z屬性的物件,在考慮z軸自然重力加速度的情況下,告訴你在每個方向上的加速度。
  • interval:以毫秒錶示的時間值,必須在另一個devicemotion事件觸發前傳入。這個值在每個事件中應該是一個常量。
  • rotationRate:一個包含表示方向的alpha、beta和gamma屬性的物件。

觸控和手勢事件

觸控事件

具體來說有以下幾個觸控事件:

  • touchstart:當手指觸控螢幕時觸發;即使已經有一個手指放在了螢幕上也會觸發。
  • touchmove:當手指在螢幕上滑動時連續地觸發。在這個事件發生期間,呼叫preventDefault()可以阻止滾動。
  • touchend: 當手指從螢幕上移開時觸發。
  • touchcancel:當系統停止跟蹤觸控時觸發。

上面這幾個事件都會冒泡,也都可以取消。雖然這些觸控事件沒有在DOM規範中定義,但它們卻是以相容DOM的方式實現的。因此,每個觸控事件的event物件都提供了在滑鼠事件中常見的屬性。

除了常見的DOM屬性外,觸控事件還包含下列三個用於跟蹤觸控的屬性。

  • touches: 表示當前跟蹤的觸控操作的Touch物件的陣列
  • targetTouchs:特定於事件目標的Touch物件的陣列
  • changedTouches:表示自上次觸控以來發生了什麼改變的Touch物件的陣列

手勢事件

有三個手勢事件,分別介紹如下。

  • gesturestart: 當一個手指已經按在螢幕上而另一個手指又觸控螢幕時觸發。
  • gesturechange:當觸控螢幕的任何一個手指的位置發生變化時觸發。
  • gestureend:當任何一個手指從螢幕上面移開時觸發。

只有兩個手指都觸控到事件的接收容器時才會觸發這些事件。在一個元素上設定事件處理程式。如果另一個手指又放在了螢幕上,則會先觸發gesturestart事件,隨後觸發基於該手指的touchstart事件。如果一個或兩個手指在螢幕上滑動,將會觸發gesturechange事件。但只要有一個手指移開,就會觸發gestureend事件,緊接著又會觸發基於該手指的touchend事件。

觸控事件和手勢事件之間存在某種關係。當一個手指放在螢幕上時,會觸發touchstart事件。如果另一個手指又放在了螢幕上,則會先觸發gesturestart事件,隨後觸發基於該手指的touchstart事件。如果一個或兩個手指在螢幕上滑動,將會觸發gesturechange事件。但只要有一個手指移開,就會觸發gestureend事件,緊接著又會觸發基於該手指的touchend事件。

與觸控事件一樣,每個手勢事件的event物件都包含著標準的滑鼠事件屬性:bubbles、cancelable、view、clientX、clientY、screenX、screenY、detail、altKey、shiftKey、ctrlKey、metaKey。此外,還包含兩個額外的屬性:rotation和scale。

其中,rotation屬性表示手指變化引起的旋轉角度,負值表示逆時針旋轉,正值表示順時針旋轉。而scale屬性表示兩個手指間距離的變化情況。

觸控事件也會返回rotation和scale屬性,但這兩個屬性只會在兩個手指與螢幕保持接觸時才會發生變化。一般來說,使用基於兩個手指的手勢事件,要比管理觸控事件中的所有互動要容易得多。

記憶體和效能

在javascript中,新增到頁面上的事件處理程式數量將直接關係到頁面的整體執行效能。導致這一問題的原因是多方面。首先,每個函式都是物件,都會佔用記憶體;記憶體中的物件越多,效能就越差。其次,必須事先指定所有事件處理程式而導致的DOM訪問次數,會延遲整個頁面的互動就緒時間。事實上,從如何利用好事件處理程式的角度出發,還是有一些方法能夠提升效能的。

事件委託

對“事件處理程式過多”問題的解決方案就是事件委託

在document物件新增一個事件處理程式,用以處理頁面上發生的某種特定型別的事件。這樣做與採取傳統的做法相比具有如下優點。

  • document物件很快就可以訪問,而且可以在頁面生命週期的任何時點上為它新增事件處理程式(無需等待DOMContentLoaded或load事件)。換句話說,只要可單擊的元素呈現在頁面上,就可以立即具備適當的功能。
  • 在頁面中設定事件處理程式所需的時間更少。只新增一個事件處理程式所需的DOM引用更少,所花的時間也更少。
  • 整個頁面佔用的記憶體空間更少,能夠提升整體效能。

移除事件處理程式

每當將事件處理程式指定給元素時,執行中的瀏覽器程式碼與支援頁面互動的JavaScript程式碼之間就會建立一個連線。這種連線越多,頁面執行起來就越慢。如前所述,可以採用事件委託技術,限制建立的連線數量。另外,在不需要的時候移除事件處理程式,也是解決這個問題的一種方案。記憶體中留有那些過時不用的“空事件處理程式”(dangling event handler),也是造成web應用程式記憶體與效能問題的主要原因。

在兩種情況下。可能會造成上述問題。

  • 第一種情況就是從文件中移除帶有事件處理程式的元素時。可能是使用removeChild()和replaceChild()方法時。更多的是發生在使用innerHTML替換頁面中某一部分的時候。如果帶有事件處理程式的元素被innerHTML刪除了,那麼原來新增到元素中的事件處理程式極有可能無法被當作垃圾回收。
  • 解除安裝頁面的時候。如果在頁面被解除安裝之前咩有清理乾淨事件處理程式,那它們就會滯留記憶體中。

模擬事件

事件經常由使用者操作或通過其他瀏覽器功能來觸發。但很少有人直到,也可以使用JavaScript在任意時刻來觸發特定的事件,而此時的事件就如同瀏覽器建立的事件一樣。

DOM中的事件模擬

參考customEvent api

小結

事件是將Javscript與網頁聯絡在一起的主要方式。“DOM3級事件”規範和HTML5定義了常見的大多數事件。即使有規範定義了基本事件,但很多瀏覽器仍然在規範之外實現了自己的專有事件,從而為開發人員提供更多掌握使用者互動的手段。

在使用事件時,需要考慮如下一些記憶體與效能方面的問題。

  • 有必要限制一個頁面中事件處理程式的數量,數量太多會導致佔用大量記憶體,而且也會讓使用者感覺頁面反應不夠靈敏。
  • 建立在事件冒泡機制之上的事件委託技術,可以有效地減少事件處理程式的數量。
  • 建議在瀏覽器解除安裝頁面之前移除頁面中的所有事件處理程式。

事件是JavaScript中最重要的主題之一,深入理解事件的工作機制以及它們對效能的影響至關重要。

相關文章