【JavaScript高級程序設計】事件

JavaScript與HTML的交互是經過事件實現的,事件表明文檔或瀏覽器窗口中某個有意義的時刻。javascript

可使用僅在事件發生時執行的監聽器(即處理程序)訂閱事件。=>觀察者模式 => 頁面行爲(JavaScript中定義)與頁面展現(HTML和CSS定義)的分離。html

最先的事件是爲了把某些表單處理工做從服務器轉移到瀏覽器上來。DOM2開始嘗試以符合邏輯的方式來標準化DOM事件。IE8是最後一個使用專有事件系統的主流瀏覽器。java

事件

事件流

頁面哪一個部分擁有特定的事件呢?當你點擊一個按鈕時,實際上不光點擊了這個按鈕,還點擊了它的容器以及整個頁面。數組

事件流描述了頁面接收事件的順序。瀏覽器

IE支持事件冒泡流,Netscape Communicator支持事件捕獲流緩存

事件冒泡

從最具體的元素開始觸發,而後向上傳播至沒有那麼具體的元素(document)。安全

點擊事件:被點擊的元素最早觸發click事件,而後click事件沿DOM樹一路向上,在通過的每一個節點上依次觸發,直至到達document對象。服務器

現代瀏覽器中的事件會一直冒泡到window對象。編輯器

事件捕獲

最不具體的節點(document)最早收到事件,而最具體的節點最後收到事件。=>爲了在事件到達最終目標前攔截事件ide

點擊事件:最早由document元素捕獲,而後沿DOM樹依次向下傳播,直至到達實際的目標元素。

現代瀏覽器都是從window對象開始捕獲事件。DOM2 Events規範規定的是從document開始。

因爲舊版本瀏覽器不支持,一般建議使用事件冒泡,特殊狀況下可使用事件捕獲。

DOM事件流

DOM2 Events規範規定事件流分爲3個階段:事件捕獲、到達目標和事件冒泡。

  1. 事件捕獲:最早發生,爲提早攔截事件提供了可能;
  2. 實際的目標元素接收到事件;
  3. 冒泡:最遲要在這個階段響應事件。

在DOM事件流中,實際的目標在捕獲階段不會接收到事件。下一階段會在實際目標元素上觸發事件的「到達目標」階段,一般在事件處理時被認爲是冒泡階段的一部分;而後冒泡階段開始,事件反向傳播至文檔。

雖然DOM2 Events規範明確捕獲階段不命中事件目標,但現代瀏覽器都會在捕獲階段在事件目標上觸發事件。=> 在事件目標上有兩個機會來處理事件。

全部現代瀏覽器都支持DOM事件流,只有IE8及更早版本不支持。

capture phase  | |  / \ bubbling up
-----------------| |--| |-----------------
| element1       | |  | |                |
|   -------------| |--| |-----------     |
|   |element2    \ /  | |          |     |
|   --------------------------------     |
|        W3C event model                 |
------------------------------------------

事件處理程序

事件是用戶或瀏覽器執行的某種動做。如click、load等。

爲響應事件而調用的函數被稱爲事件處理程序(或事件監聽器)。事件處理程序的名字以「on」開頭。

有多種方式能夠指定事件處理程序。

HTML事件處理程序

特定元素支持的每一個事件均可以使用事件處理程序的名字(onxxx)以HTML屬性的形式來指定。此時屬性的值必須是可以執行的JavaScript代碼。

由於屬性的值是JavaScript代碼,因此不能在未經轉義的狀況下使用HTML語法字符,如&、"、<和>。爲避免使用HTML實體,可使用單引號代替雙引號,或者使用\&quot;。

在HTML中定義的事件處理程序能夠包含精確的動做指令,也能夠調用在頁面其餘地方定義的腳本。做爲事件處理程序執行的代碼能夠訪問全局做用域中的一切。

以這種方式指定的事件處理程序有一些特殊的地方

  1. 會建立一個函數來封裝屬性的值。這個函數有一個特殊的局部變量event,即event對象;
  2. 在這個函數中,this值至關於事件的目標元素;
  3. 這個動態建立的包裝函數,其做用域鏈被擴展了。=> document和元素自身的成員均可以被當成局部變量來訪問。這是經過使用with實現的。

    // 實際上的包裝函數是onclick屬性的值
    function () {
      with(document) {
        with(this) {
          // ... HTML事件處理程序屬性值
        }
      }
    }

=> 事件處理程序能夠更方便地訪問本身的屬性(不用帶this.)

若是元素是一個表單輸入框,則做用域鏈中還會包含表單元素 => 事件處理程序的代碼能夠沒必要引用表單元素,而直接訪問同一表單中的其餘成員了(經過name屬性)。

在HTML中指定事件處理程序存在的問題

  1. 時機問題。有可能HTML元素已經顯示在頁面上,但事件處理程序的代碼還沒法執行。=> 大多數HTML事件處理程序會封裝在try/catch塊中,以便在這種狀況下靜默失敗。

    <input type="button" value="Click Me" onclick="try{doSomething();}catch(ex){}">
  2. 對事件處理程序做用域鏈的擴展在不一樣瀏覽器中可能致使不一樣的結果。不一樣JavaScript引擎中標識符解析的規則存在差別 => 訪問無限定的對象成員可能致使錯誤。
  3. HTML與JavaScript的強耦合。(若是要修改,必須在HTML和JavaScript中都修改代碼)

DOM0事件處理程序

把一個函數賦值給(DOM元素的)一個事件處理程序屬性。=> 簡單

要使用JavaScript指定事件處理程序,必須先取得要操做對象的引用

每一個元素(包括window和document)都有一般小寫的事件處理程序屬性。

賦值代碼運行以後纔會給事件處理程序賦值。

所賦函數被視爲元素的方法。=> 事件處理程序會在元素的做用域中運行,即this等於元素。

let btn = document.querySelector('#myBtn');
btn.onclick = function() {
  console.log(this.id); // "myBtn"
}

以這種方式添加事件處理程序是註冊在事件流的冒泡階段的。

經過將事件處理程序屬性的值設置爲null,能夠移除經過DOM0方式添加的事件處理程序。(在HTML中指定的事件處理程序,也能夠經過JavaScript將相應屬性設置爲null來移除)

btn.onclick = null;

DOM2事件處理程序

DOM2 Events爲事件處理程序的賦值和移除定義了兩個方法:addEventListener()和removeEventListener()。暴露在全部DOM節點上。

接收3個參數:事件名、事件處理函數和一個表示是否在捕獲階段調用處理函數的布爾值(默認值爲false,在冒泡階段調用)。

這個事件處理程序一樣在被附加到的元素的做用域中運行:this等於元素。

主要優點:能夠爲同一個事件添加多個事件處理程序。多個事件處理程序以添加順序來觸發。

經過addEventListener()添加的事件處理程序只能使用removeEventListener()並傳入與添加時一樣的參數來移除。(事件處理函數必須是同一個)

大多數狀況下,事件處理程序會被添加到事件流的冒泡階段,主要緣由是跨瀏覽器兼容性好。(除非須要在事件到達其指定目標以前攔截事件)

IE事件處理程序

IE實現了與DOM相似的方法,attachEvent()和detachEvent()。

接收2個參數:事件處理程序的名字和事件處理函數。由於IE8及更早版本只支持事件冒泡,因此使用attachEvent()添加的事件處理程序會添加到冒泡階段。

在IE中使用attachEvent()與使用DOM0方式的主要區別在於事件處理程序的做用域。使用attachEvent()時,事件處理程序是在全局做用域中運行的,所以this等於window。

attachEvent()方法也能夠給一個元素添加多個事件處理程序。以添加它們的順序反向觸發

使用attachEvent()添加的事件處理程序將使用detachEvent()來移除,只要提供相同的參數(處理函數是相同的函數引用)。

let btn = document.querySelector('#myBtn');
var handler = function() {
  console.log("Clicked");
};
btn.attachEvent("onclick", handler);
btn.detachEvent("onclick", handler);

跨瀏覽器事件處理程序

以跨瀏覽器兼容的方式處理事件。

本身編寫跨瀏覽器事件處理代碼主要依賴能力檢測。要確保最大兼容性,只要讓代碼在冒泡階段運行便可。

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 { // 默認DOM0方式
      element["on"+type] = handler;
    }
  },
  removeHandler: function(element, type, handler) {
    if(element.removeEventListener) {
      element.removeEventListener(type, handler, false);
    } else if(element.detachEvent) {
      element.detachEvent("on"+type, handler);
    } else { // 默認DOM0方式
      element["on"+type] = null;
    }
  }
};

// 使用
let btn = document.querySelector('#myBtn');
let handler = function() {
  console.log("Clicked");
};
EventUtil.addHandler(btn, "click", handler);
EventUtil.removeHandler(btn, "click", handler);

沒有解決的存在的問題:

  1. IE的做用域問題。(attachEvent()中this等於window)
  2. 多個事件處理程序執行順序問題。DOM2以添加順序,IE爲添加順序的反向
  3. DOM0只支持給一個事件添加一個處理程序。(DOM0瀏覽器已經不多人使用,問題應該不大)

事件對象event

在DOM中發生事件時,全部相關信息(如事件目標元素、事件類型)都會被收集並存儲在一個名爲event的對象中。

DOM事件對象

在DOM合規的瀏覽器中,event對象是傳給事件處理程序的惟一參數。無論以哪一種方式(DOM0或DOM2)指定事件處理程序,都會傳入這個event對象。在經過HTML屬性指定的事件處理程序中,一樣可使用變量event引用事件對象。

全部事件對象包含的公共屬性和方法:

  • bubbles。布爾值。是否冒泡
  • cancelable。布爾值。是否能夠取消事件的默認行爲
  • currentTarget。元素。當前事件處理程序所在的元素
  • defaultPrevented。布爾值。true表示已經調用preventDefault()方法。(DOM3 Events新增)
  • detail。整數(??)。事件相關的其餘信息
  • eventPhase。整數。表示調用事件處理程序的階段:1-捕獲階段;2-到達目標;3-冒泡階段
  • target。元素。事件目標元素
  • trusted。布爾值。true表示事件由瀏覽器生成;false表示事件由開發者經過JavaScript建立。(DOM3 Events新增)
  • type。字符串。被觸發的事件類型
  • View。AbstractView。與事件相關的抽象視圖;等於事件所發生的window對象
  • preventDefault()。函數。用於取消事件的默認行爲 => cancelable爲true時纔可調用
  • stopImmediatePropagation()。函數。用於取消全部後續事件捕獲或事件冒泡,並阻止調用任何後續事件處理程序。(DOM3 Events新增)
  • stopPropagation()。函數。用於取消全部後續事件捕獲或事件冒泡 => bubbles爲true時纔可調用

在事件處理程序內部,this始終等於currentTarget。this === event.currentTarget

若是事件處理程序直接添加在乎圖的目標,則this、currentTarget和target三者相等。

type屬性在一個處理程序處理多個事件時頗有用:根據事件類型,作出不一樣的響應。

preventDefault()可阻止特定事件的默認動做,如連接的默認行爲是被單擊時導航到href屬性指定的URL。任何可調用preventDefault()取消默認行爲的事件,其event對象的cancelable屬性都會設置爲true。

stopPropagation()用於當即阻止事件流在DOM結構中傳播,取消後續的事件捕獲或冒泡。

eventPhase屬性可用於肯定事件流當前所處的階段。若是事件處理程序在目標上被調用,則eventPhase等於2 => 雖然」到達目標「是在冒泡階段發生的,但eventPhase等於2。=> 當eventPhase等於2,this、currentTarget和target三者相等。

event對象只在事件處理程序執行期間存在,一旦執行完畢,就會被銷燬。

IE事件對象

IE事件對象能夠基於事件處理程序被指定的方式以不一樣的方式來訪問。

  • 若是使用DOM0方式指定,則event對象是window的一個屬性
  • 若是使用attachEvent()指定,則event對象會做爲惟一的參數傳給處理函數。此時event對象仍然是window的屬性,出於方便也將其做爲參數傳入
  • 在經過HTML屬性方式指定的事件處理程序中,一樣可使用變量event引用事件對象。

全部IE事件對象都會包含的公共屬性和方法:

  • cancelBubble。布爾值。讀/寫。true表示取消冒泡(默認false),與stopPropagation()方法效果類似
  • returnValue。布爾值。讀/寫。false表示取消事件默認行爲(默認true),與preventDefault()效果相同
  • srcElement。元素。事件目標,即target
  • type。字符串。觸發的事件類型

事件處理程序的做用域取決於指定它的方式,因此更好的方式是使用事件對象的srcElement屬性代替this。(DOM0方式下,this等於元素;attachEvent()方式下,this等於window)

與DOM不一樣,沒法經過JavaScript肯定事件是否能夠被取消。

cancelBubble屬性與stopPropagation()方法用途類似,但IE8及更早版本不支持捕獲階段,因此只會取消冒泡。

跨瀏覽器事件對象

DOM事件對象中包含IE事件對象的全部信息和能力,只是形式不一樣。這些共性可以讓兩種事件模型之間的映射成爲可能。

var EventUtil = {
  addHandler: function(element, type, handler) {
    // ...
  },
  removeHandler: function(element, type, handler) {
    // ...
  },
  getEvent: function(event) { // IE事件中以DOM0方式指定事件處理程序時,event對象是window的一個屬性
    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;
    }
  }
};

// 使用
let btn = document.querySelector('#myBtn');
btn.onclick = function(event) {
  event = EventUtil.getEvent(event);
  let target = EventUtil.getTarget(event);
  EventUtil.preventDefault(event); // 阻止事件的默認行爲
  EventUtil.stopPropagation(event); // 阻止事件冒泡
};

事件類型

所發生事件的類型決定了事件對象中會保存什麼信息。DOM3 Events定義的事件類型:

  • 用戶界面事件(UIEvent):與BOM交互的通用瀏覽器事件
  • 焦點事件(FocusEvent):元素得到和失去焦點時觸發
  • 鼠標事件(MouseEvent):鼠標在頁面上執行某些操做時觸發
  • 滾輪事件(WheelEvent):使用鼠標滾輪(或相似設備)時觸發
  • 輸入事件(InputEvent):向文檔中輸入文本時觸發
  • 鍵盤事件(KeyboardEvent):鍵盤在頁面上執行某些操做時觸發
  • 合成事件(CompositionEvent):使用某種IME(Input Method Editor,輸入法編輯器)輸入字符時觸發
  • HTML5還定義了另外一組事件
  • 瀏覽器一般在DOM和BOM上實現專有事件:根據開發者需求,不一樣瀏覽器的實現可能不一樣

DOM3 Events在DOM2 Events基礎上從新定義了事件,並增長了新的事件類型。全部主流瀏覽器都支持DOM2 Events和DOM3Events。

用戶界面事件 UIEvent

不必定跟用戶操做有關。保留它們是爲了向後兼容。主要有如下幾種:

  • DOMActivate(DOM3 Events中已經廢棄)。元素被用戶經過鼠標或鍵盤操做激活時觸發,瀏覽器實現之間存在差別
  • load。window(頁面加載完成後觸發);窗套frameset(全部窗格frame都加載完成後觸發);img(圖片加載完成後觸發);object(相應對象加載完成後觸發)
  • unload。window(頁面徹底卸載後觸發);窗套(全部窗格都卸載完成後觸發);object(相應對象卸載完成後觸發)
  • abort。object(相應對象加載完成前被用戶提早終止下載時觸發)
  • error。window(JavaScript報錯時觸發);img(沒法加載指定圖片時觸發);object(沒法加載相應對象時觸發);窗套(一個或多個窗格沒法完成加載時觸發)
  • select。在文本框(input或textarea)上用戶選擇了一個或多個字符時觸發
  • resize。window或窗格(窗口或窗格被縮放時觸發)
  • scroll。當用戶滾動包含滾動條的元素時在元素上觸發。body元素包含已加載頁面的滾動條

大多數HTML事件與window對象和表單控件有關。除了DOMActivate,其餘在DOM2 Events中都被歸爲HTML Events。(DOMActivate是UI事件)

焦點事件 FocusEvent

頁面元素得到或失去焦點時觸發。能夠與document.hasFocus()和document.activeElement一塊兒爲開發者提供用戶在頁面中導航的信息。焦點事件有如下6種:

  • blur。失去焦點時觸發。不冒泡,全部瀏覽器都支持
  • DOMFocusIn(DOM3 Events中已經廢棄,推薦focusin)。得到焦點時觸發。focus的冒泡版。Opera惟一支持
  • DOMFocusOut(DOM3 Events中已經廢棄,推薦focusout)。失去焦點時觸發,blur的通用版。Opera惟一支持
  • focus。得到焦點時觸發。不冒泡,全部瀏覽器都支持
  • focusin。得到焦點時觸發。focus的冒泡版
  • focusout。失去焦點時觸發。blur的通用版

兩個主要事件是focus和blur,它們最大的問題是不冒泡。

當焦點從頁面中的一個元素A移到另外一個元素B上,會依次發生以下事件(測試,與書中不一致):

1)A:blur

2)A:focusout

3)B:focus

4)B:focusin

DOMFocusOut和DOMFocusIn未驗證

鼠標和滾輪事件MouseEvent

鼠標是用戶的主要定位設備。DOM3 Events定義了9種鼠標事件:

  • click。用戶單擊鼠標主鍵(一般是左鍵)或按鍵盤迴車鍵時觸發。
  • dblclick。用戶雙擊鼠標主鍵(一般是左鍵)時觸發(DOM3 Events中標準化)
  • mousedown。用戶按下任意鼠標鍵時觸發。不能經過鍵盤觸發
  • mouseenter。用戶把鼠標光標從元素外部移到元素內部時觸發。不冒泡,也不會在光標通過後代元素時觸發。(DOM3 Events中新增)
  • mouseleave。用戶把鼠標光標從元素內部移到元素外部時觸發。不冒泡,也不會在光標通過後代元素時觸發。(DOM3 Events中新增)
  • mousemove。鼠標光標在元素上移動時反覆觸發。不能經過鍵盤觸發
  • mouseout。用戶把鼠標光標從一個元素移到另外一個元素上(外部元素或子元素)時觸發。不能經過鍵盤觸發
  • mouseover。用戶把鼠標光標從元素外部移到元素內部時觸發。不能經過鍵盤觸發
  • mouseup。用戶釋放鼠標鍵時觸發。不能經過鍵盤觸發

頁面中全部元素都支持鼠標事件。除了mouseenter和mouseleave,其餘鼠標事件都會冒泡,均可以被取消,這會影響瀏覽器的默認行爲。因爲事件之間存在關係,所以取消鼠標事件的默認行爲也會影響其餘事件。

雙擊鼠標主鍵會按以下順序觸發事件:

1)mousedown

2)mouseup

3)click

4)mousedown

5)mouseup

6)click

7)dblclick

click和dblclick在觸發前都依賴其餘事件觸發,mousedown和mouseup則不會受其餘事件影響。

IE8和更早的版本的實現中存在問題,會致使雙擊事件跳過第二次mousedown和click事件。

1)mousedown

2)mouseup

3)click

4)mouseup

5)dblclick

DOM3 Events中鼠標事件對應的類型是」MouseEvent「(單數形式)

鼠標事件還有一個名爲滾輪事件的子類別。滾輪事件只有一個事件mousewheel,對應鼠標滾輪或帶滾輪的相似設備上滾輪的交互。

鼠標事件event對象的一些屬性:

  1. 客戶端座標

    注:客戶端座標不考慮頁面滾動

  2. 頁面座標

    在頁面上的位置。表示事件發生時鼠標光標在頁面上的座標,經過event對象的pageX和pageY屬性獲取。

    反映的是光標到頁面而非視口左邊與上邊的距離。

  3. 屏幕座標

    鼠標光標在屏幕上的座標,經過event對象的screenX和screenY屬性獲取。

  4. 修飾鍵

    有時要肯定用戶想實現的操做,還要考慮鍵盤按鍵的狀態

    鍵盤上的修飾鍵Shift、Ctrl、Alt和Meta(win的window鍵,mac的command鍵)常常用於修改鼠標事件的行爲。

    4個屬性來表示這幾個修飾鍵的狀態:shiftKey、ctrlKey、altKey、metaKey。(被按下爲true,不然爲false)

    如今瀏覽器支持全部4個修飾鍵,IE8及更早版本不支持metaKey屬性。

  5. 相關元素

    對mouseover和mouseout事件而言,還存在與事件相關的其餘元素

  6. 鼠標按鍵

    對mousedown和mouseup事件來講,event對象上會有一個button屬性,表示按下或釋放的是哪一個按鍵。DOM爲button屬性定義了3個值:0-主鍵;1-中鍵(一般是滾輪鍵);2-副鍵。

    IE8及更早版本也提供了button屬性,考慮了同時按多個鍵的狀況。

  7. 額外事件信息

    DOM2 Events規範在event對象上提供了detail屬性,以給出關於事件的更多信息。對鼠標事件來講,detail包含一個數值,表示在給定位置上發生了多少次單擊(連續單擊)。每次單擊會加1。連續點擊中斷會重置爲0。

    IE還爲每一個鼠標事件提供瞭如下額外信息:

    • altLeft,布爾值,是否按下了左Alt鍵(若是爲true,則altKey也爲true)
    • ctrlLeft,布爾值,是否按下左Ctrl鍵(若是爲true,則ctrlKey也爲true)
    • offsetX,光標相對於目標元素邊界的x座標
    • offsetY,光標相對於目標元素邊界的y座標
    • shiftLeft,布爾值,是否按下了左Shift鍵(若是爲true,則shiftKey也爲true)

滾輪mousewheel事件

在用戶使用鼠標滾輪時觸發,包括在垂直方向上任意滾動。會在任何元素上觸發,並(在IE8中)冒泡到document和(全部現代瀏覽器中)window。

event對象包含鼠標事件的全部標準信息,此外還有一個名爲wheelDelta的屬性。

多數狀況下只需知道滾輪滾動的方向,而這經過wheelDelta值的符號就能夠知道。(向前滾動一次+120,向後滾動一次-120)

觸摸屏設備

觸摸屏一般不支持鼠標操做。

  • 不支持dblclick事件。(測試一加三能夠)
  • 單指點觸屏幕上的可點擊元素會觸發mousemove事件。(測試一加三不行)可點擊元素是指點擊時有默認動做的元素(如連接)或指定了onclick事件處理程序的元素
  • mousemove事件也會觸發mouseover和mouseout事件。(還未測試)
  • 雙指點觸屏幕並滑動致使頁面滾動時會觸發mousewheel和scroll事件。(還未測試)

無障礙問題

若是Web應用或網站要考慮殘障人士,特別是使用屏幕閱讀器的用戶,那麼必須當心使用鼠標事件(除了回車鍵能夠觸發click事件,其餘鼠標事件不能經過鍵盤觸發)。建議不要使用click事件以外的其餘鼠標事件向用戶提示功能或觸發代碼執行。=> 會嚴格妨礙盲人或視障用戶使用。

幾條使用鼠標事件時應該遵循的無障礙建議

  • 使用click事件執行代碼。當使用onmousedown執行代碼時,應用程序會運行得更快,但屏幕閱讀器沒法觸發mousedown事件
  • 不要使用mouseover向用戶顯示新選項。沒法觸發。能夠考慮鍵盤快捷鍵
  • 不要使用dblclick執行重要的操做。沒法觸發

更多網站無障礙的信息,能夠參考WebAIM網站。

鍵盤與輸入事件KeyboardEvent

用戶操做鍵盤時觸發。很大程度上是基於原始的DOM0實現的。

DOM3 Events爲鍵盤事件提供了一個首先在IE9中徹底實現的規範,其餘瀏覽器也開始實現該規範,但仍存在不少遺留的實現。

包含3個事件:

  • keydown,按下鍵盤上某個鍵時觸發,持續按鈕會重複觸發
  • keypress(DOM3 Events已經廢棄,推薦textInput事件),按下鍵盤上某個鍵併產生字符時觸發,持續按住會重複觸發。Esc鍵也會觸發(Chrome裏測試不會觸發?)。
  • keyup,用戶釋放鍵盤上某個鍵時觸發。

全部元素都支持這些事件。但在文本框中輸入內容時最容易看到。

輸入事件只有一個:textInput。是對keypress事件的擴展,用於在文本顯示給用戶以前更方便地截獲文本輸入。會在文本被插入到文本框以前觸發。

  1. 當用戶按下某個字符鍵時,會觸發keydown事件,而後觸發keypress事件,最後觸發keyup事件。(keydown和keypress會在文本框出現變化以前觸發,keyup會在文本框出現變化以後觸發);若是按住不放,keydown和keypress會重複觸發,直到這個鍵被釋放。
  2. 當用戶按下非字符鍵時,會觸發keydown事件,而後觸發keyup事件。若是按住不放,keydown會重複觸發,直到這個鍵被釋放。

注:鍵盤事件支持與鼠標事件相同的修飾鍵。

鍵盤事件event對象的一些屬性:

  1. 鍵碼keyCode

    keydown和keyup事件,event對象的keyCode屬性會保存一個鍵碼。

    對於字母和數字鍵,keyCode的值與大寫字母和數字的ASCII編碼一致。與是否按了Shift鍵無關。

    DOM和IE的event對象都支持keyCode屬性

  2. 字符編碼charCode

    keypress事件,意味着按鍵會影響屏幕上顯示的文本。對插入或移除字符的鍵,全部瀏覽器都會觸發keypress事件,其餘鍵則取決於瀏覽器。

    event對象上的charCode屬性,只有發生keypress事件時這個屬性纔會被設置值(此時與keyCode屬性相等)。包含的是按鍵字符對應的ASCII編碼。

    IE8及更早版本和Opera使用keyCode傳達字符的ASCII編碼。要以跨瀏覽器方式獲取字符編碼,首先要檢測charCode屬性是否有值,若是沒有再使用keyCode。

    有了字母編碼,就可使用String.fromCharCode()方法將其轉換爲實際的字符了。

DOM3的變化

DOM3 Events規範並未規定charCode屬性,而是定義了key和char兩個新屬性。

key屬性用於替代keyCode,且包含字符串。按下字符鍵時,key等於文本字符;按下非字符鍵時,key的值是鍵名(如」Shift「或」ArrowDown「)

char屬性在按下字符鍵時與key相似,在按下非字符鍵時爲null。(測試Chrome中keypress和keydown的event對象此屬性都無)

IE支持key屬性但不支持char屬性。

Safari和Chrome支持keyIdentifier屬性(測試Chrome無此屬性)。對於字符鍵,keyIdentifier返回以」U+0000「形式表示Unicode值的字符串形式的字符編碼。

因爲缺少跨瀏覽器支持,不建議使用key、keyIdentifier和char。

DOM3 Events也支持一個名爲location的屬性,是一個數值,表示是在哪裏按的鍵。可能的值爲:0-默認鍵;1-左邊;2-右邊;3-數字鍵盤;4-移動設備(虛擬鍵盤);5-遊戲手柄。Safari和Chrome支持一個等價的keyLocation屬性(實現有問題)

沒有獲得普遍支持,不建議在跨瀏覽器開發時使用location屬性。

給event對象增長了getModifierState()方法,接收一個參數,一個等於Shift、Control、Alt、AltGraph或Meta的字符串,表示要檢測的修飾鍵。若是給定 的修飾鍵被按鈕,則返回true。(也可直接使用event對象的shiftKey、ctrlKey、altKey或metaKey屬性獲取)

輸入textInput事件

DOM3 Events規範新增,在字符被輸入到可編輯區域時觸發。

與keypress比對:1. keypress會在任何能夠得到焦點的元素上觸發,textInput只在可編輯區域上觸發;2. textInput只在有新字符被插入時纔會觸發,而keypress對任何可能影響文本的鍵都會觸發(包括退格鍵(Chrome裏測試不會觸發?))。3. 使用輸入法(搜狗)時,在觸發合成事件時不會觸發keypress,在compositionend觸發以前會先觸發textInput事件。4. 使用鍵盤輸入先觸發keypress,再觸發textInput。

該事件主要關注字符,event對象上有data屬性,爲被插入的字符(非字符編碼);還有一個inputMethod的屬性(Chrome中測試無此屬性打印爲undefined),表示向控件中輸入文本的手段,能夠輔助驗證

設備上的鍵盤事件(非鍵盤)

任天堂Wii

合成事件

DOM3 Events中新增,用於處理一般使用IME輸入時的複雜輸入序列。IME可讓用戶輸入物理鍵盤上沒有的字符,一般須要按下多個鍵才能輸入一個字符,合成事件用於檢測和控制這種輸入。

合成事件有如下3種:

  • compositionstart,表示輸入即將開始
  • compositionupdate,在新字符插入輸入字段時觸發;
  • compositionend,表示恢復正常鍵盤輸入

惟一增長的事件屬性是data:

  • 在compositionstart中,爲正在編輯的文本(默認是空串,或者是選中的文本)
  • 在compositionupdate中,爲要插入的新字符
  • 在compositionend中,爲本次合成過程當中輸入的所有內容

測試獲得的觸發順序:

keydown -> ...start -> ...update -> ( (keyup) -> keydown -> ...update -> (keyup))(循環觸發) -> ...update(此時data與...end事件中的data一致) -> textInput -> ...end -> keyup

變化事件

DOM2的變化事件(Mutation Events),在DOM發生變化時提供通知。

(已廢棄)

已經被Mutation Observers所取代(第14章)

HTML5事件

HTML5中獲得瀏覽器較好支持的一些事件(規範未涵蓋)

  1. contextmenu事件

    單擊鼠標右鍵(Ctrl+單擊左鍵)。用於容許開發者取消默認的上下文菜單並提供自定義菜單。冒泡。

    事件目標是觸發操做的元素,這個事件在全部瀏覽器中均可以取消(event.preventDefault()或event.returnValue設置爲false)。

    一般經過onclick事件處理程序觸發隱藏(自定義菜單)。

  2. beforeunload事件

    在window上觸發。用於給開發者提供阻止頁面被卸載的機會。在頁面即將從瀏覽器中卸載時觸發。

    不能取消,不然就意味着能夠把用戶永久阻攔在一個頁面上。

    該事件會向用戶顯示一個確認框。用戶能夠點擊取消或者確認離開頁面。須要將event.returnValue設置爲要在確認框中顯示的字符串(對於IE和FF來講)(測試FF顯示的提示文字與returnValue屬性值無關),並將其做爲函數值返回(對於Safari和Chrome來講)(測試Chrome無返回值也無影響)

  3. DOMContentLoaded事件

    會在DOM樹構建完成後當即觸發,而不用等待圖片、JavaScript文件、CSS文件或其餘資源加載完成。(能夠在外部資源下載的同時指定事件處理程序,從而讓用戶可以更快地與頁面交互)

    比對load事件:要等待不少外部資源加載完成。

    須要給document或window添加事件處理程序(實際的事件目標是document,會冒泡到window)。

    一般用於添加事件處理程序或執行其餘DOM操做。這個事件始終在load事件以前觸發。

    對於不支持DOMContentLoaded事件的瀏覽器,可使用超時爲0的setTimeout()函數,經過其回調來設置事件處理程序。本質上是在當前JavaScript進程執行完畢後當即執行這個回調。(與DOMContentLoaded觸發時機一致無絕對把握,最好是頁面上的第一個超時代碼)

  4. readystatechange事件

    IE首先定義。用於提供文檔或元素加載狀態的信息,但行爲有時不穩定。

    event.target或其餘支持readystatechange事件的對象都有一個readyState屬性,該屬性可能爲如下5個值:

    • uninitialized:對象存在並還沒有初始化
    • loading:對象正在加載數據
    • loaded:對象已經加載完數據
    • interactive √:對象能夠交互,但還沒有加載完成
    • complete √:對象加載完成

    並不是全部對象都會經歷全部readyState階段(Chrome測試document只經歷了兩個階段:interactive和complete)

    值爲」interactive「的readyState階段,時機相似於DOMContentLoaded。進入交互階段,意味着DOM樹已加載完成。(此時圖片和其餘外部資源不必定都加載完成了)。

    與load事件共同使用時,這個事件的觸發順序不能保證。interactive和complete的順序也不是固定的,爲了搶到較早的時機,須要同時檢測交互階段和完成階段(能夠保證儘量接近使用DOMContentLoaded事件的效果)。

  5. pageshow與pagehide事件

    FF和Opera開發的一個名爲往返緩存(bfcache,back-forward cache)的功能,旨在使用瀏覽器」前進「和」後退「按鈕時加快頁面之間的切換。不只存儲頁面數據,也存儲DOM和JavaScript狀態,其實是把整個頁面都保存在內存裏。

    若是頁面在緩存中,導航到這個頁面時就不會觸發load事件。

    • pageshow:在頁面顯示時觸發,不管是否來自往返緩存。新加載的頁面,會在load事件以後觸發;來自往返緩存的頁面,會在頁面狀態徹底恢復後觸發。事件目標是document,但事件處理程序必須添加到window上。(點擊了瀏覽器的」刷新「按鈕,頁面會從新加載)。event對象中的persisted屬性爲布爾值,表示頁面內容是否來自往返緩存。
    • pagehide:在頁面從瀏覽器中卸載後,在unload事件以前觸發。事件目標是document,但事件處理程序必須添加到window上。event對象中的persisted屬性爲布爾值,表示頁面在卸載後是否保存在往返緩存中。

      註冊了onunload事件處理程序的頁面會自動排除在往返緩存以外(測試beforeunload也會影響),由於onunload的典型場景就是撤銷onload事件發生時所作的事情,若是使用往返緩存,下一次頁面顯示時就不會觸發onload事件,這可能致使頁面沒法使用。

  6. hashchange事件

    用於在URL散列值(#後面的部分)發生變化時通知開發者。

    事件處理程序必須添加給window。event對象有兩個新屬性:oldURL和newURL,分別保存變化先後的URL,包含散列值的完整URL。若是想肯定當前的散列值,最好使用location對象。

設備事件

智能手機和平板計算機=>交互的新方式

用於肯定用戶使用設備的方式。

  1. orientationchange事件

    蘋果,移動Safari瀏覽器。判斷用戶的設備是處於垂直模式仍是水平模式。window.orientation屬性,有3種值:0-垂直模式,90-左轉水平模式(Home鍵在右),-90-右轉水平模式(Home鍵在左)。當屬性值改變就會觸發該事件。

    全部iOS設備都支持該事件和該屬性。(測試鎖定豎屏=>不會改變)

    被認爲是window事件,也可給body元素添加onorientationchange屬性來指定事件處理程序。

  2. deviceorientation事件

    DeviceOrientationEvent規範定義的事件。

    若是能夠獲取設備的加速計信息,且數據發生了變化,就會在window上觸發。只反應設備在空間中的朝向,與移動無關。

    設備自己處於3D空間,x軸方向爲從設備左側到右側,y軸方向爲從設備底部到上部,z軸方向爲從設備背面到正面。

    event對象包含各個軸相對於設備靜置時座標值的變化,主要有5個屬性:

    • alpha:0~360內的浮點值,表示圍繞z軸旋轉時y軸的度數(左右轉)
    • beta:-180~180內的浮點值,表示圍繞x軸旋轉時z軸的度數(先後轉)
    • gamma:-90~90內的浮點值,表示圍繞y軸旋轉時z軸的度數(扭轉)。
    • absolute:布爾值,表示設備是否返回絕對值。
    • compassCalibrated:布爾值,表示設備的指南針是否正確校準。

    測試iPhone8(iOS11.4.1)平放在桌面上也一直監聽到變更(?),測試Android(一加三)平放在桌面上後不會變更

  3. devicemotion事件

    DeviceOrientationEvent規範定義的事件。

    用於提示設備實際上在移動,而不只僅是改變了朝向。event對象包含的額外屬性:

    • acceleration:對象,包含x、y和z屬性,反映不考慮重力狀況下各個維度的加速信息
    • accelerationIncludingGravity:對象,包含x、y和z屬性,反映各個維度的加速信息,包含z軸天然重力加速度
    • interval:毫秒,距離下次觸發事件的時間。此值在事件之間應爲常量。
    • rotationRate:對象,包含alpha、beta和gamma屬性,表示設備朝向。

    若是沒法提供acceleration、accelerationIncludingGravity、rotationRate信息,則屬性值爲null。=> 使用以前必須先檢測

    測試iPhone8(iOS11.4.1)平放在桌面上也一直監聽到變更,測試Android(一加三)平放在桌面上也一直監聽到變更

觸摸及手勢事件

只適用於觸屏設備。

Webkit爲Android定製了不少專有事件,成爲了事實標準,並被歸入W3C的Touch Events規範。

  1. 觸摸事件

    以下幾種:

    • touchstart:手指放到屏幕上時觸發
    • touchmove:手指在屏幕上滑動時連續觸發。在此事件中調用preventDefault()能夠阻止滾動(測試並不能)
    • touchend:手指從屏幕上移開時觸發
    • touchcancel:系統中止跟蹤觸摸時觸發。文檔未明確什麼狀況下中止跟蹤。

    都會冒泡,均可以被取消。不屬於DOM規範,瀏覽器以兼容DOM的方式實現它們。每一個觸摸事件的event對象都提供了鼠標事件的公共屬性,另外提供如下3個屬性用於跟蹤觸點:

    • touches:Touch對象的數組,表示當前屏幕上的每一個觸點。
    • targetTouches:Touch對象的數組,表示特定於事件目標的觸點。
    • changedTouches:Touch對象的數組,表示自上次用戶動做以後變化的觸點

    每一個Touch對象包含一些屬性,可用於追蹤屏幕上的觸摸軌跡。(針對一個觸點)touchend事件觸發時touches集合中什麼也沒有,這是由於沒有滾動的觸點了。

    當手指點觸屏幕上的元素時,依次觸發的事件(測試與書本不一致):

    1)touchstart

    2)touchend

    3)mousemove

    4)mousedown

    5)mouseup

    6)click

  2. 手勢事件

    iOS2.0中的Safari中增長。在兩個手指觸碰屏幕且相對距離或旋轉角度變化時觸發。有以下3種:

    • gesturestart:一個手指在屏幕上,再把另外一手指放到屏幕上時觸發
    • gesturechange:任何一個手指在屏幕上的位置發生變化時觸發
    • gestureend:其中一個手指離開屏幕時觸發

    都會冒泡。

    只有在兩個手指同時接觸事件接收者時(目標元素邊界之內),這些事件纔會觸發。

    觸摸事件和手勢事件存在必定的關係。

    每一個手勢事件的event對象都包含全部標準的鼠標事件屬性,新增了兩個屬性是rotation和scale。

    rotation:表示手指變化旋轉的度數,負值表示逆時針旋轉,正值表示順時針旋轉(從0開始);

    scale:表示兩指之間距離變化(對捏)的程度,開始時爲1,而後隨着距離增大或縮小相應地增大或縮小。

    觸摸事件也會返回rotation和scale屬性,但只在兩個手指觸碰屏幕時纔會變化。

其餘一些規範中定義的瀏覽器事件

參考書本

內存與性能

在JavaScript中,頁面中事件處理程序的數量與頁面總體性能直接相關。

首先,每一個函數都是對象,都佔用內存空間;其次,爲指定事件處理程序所需訪問DOM的次數會先期形成整個頁面交互的延遲。

改善頁面性能?

事件委託

」過多事件處理程序「的解決方案是使用事件委託。

利用事件冒泡,能夠只使用一個事件處理程序來管理一種類型的事件。只要給全部元素(須要處理某種事件的元素)共同的祖先節點添加一個事件處理程序,就能夠解決問題(根據target判斷進行不一樣的處理)。=> 只訪問了一個DOM元素和添加了一個事件處理程序。 => 佔用內存更少,全部使用按鈕的事件(大多數鼠標事件和鍵盤事件)都適用於這個解決方案。

只要可行,就應該考慮只給document添加一個事件處理程序,經過它處理頁面中全部某種類型的事件。優勢以下:

  • document對象隨時可用。=> 只要頁面渲染出可點擊的元素,就能夠無延遲地起做用。
  • 既能夠節省DOM引用,也能夠節省時間(設置頁面事件處理程序的事件)。
  • 減小整個頁面所需的內存,提高總體性能。

最適合使用事件委託的事件包括:click、mousedown、mouseup、keydown和keypress。

刪除事件處理程序

把事件處理程序指定給元素後,在瀏覽器代碼和負責頁面交互的JavaScript代碼之間就創建了聯繫。這種聯繫創建得越多,頁面性能就越差。除了使用事件委託減小這種聯繫外,還應及時刪除不用的事件處理程序。

不少Web應用性能不佳都是因爲無用的事件處理程序長駐內存致使的。緣由以下:

  • 刪除帶有事件處理程序的元素。如使用方法removeChild()或replaceChild()刪除節點,或使用innerHTML總體替換頁面的某一部分。=> 被刪除的元素上如有事件處理程序,就不會被垃圾收集程序正常清理。(特別是IE8及更早版本,元素的引用和事件處理程序的引用)

    若是知道某個元素會被刪除,那麼最好在刪除它以前手工刪除它的事件處理程序(或者不直接給它添加事件處理程序,使用事件委託)。=>確保內存被回收,元素也能夠安全地從DOM中刪掉。

    注意:在事件處理程序中刪除元素會阻止事件冒泡。只有事件目標仍然存在於文檔中時,事件纔會冒泡。

  • 頁面卸載致使內存中殘留引用。事件處理程序沒有被清理,會殘留在內存中。

    最好在onunload事件處理程序中趁頁面還沒有卸載先刪除全部事件處理程序。=> 使用事件委託的優點:事件處理程序不多。

模擬事件

一般事件都是由用戶交互或瀏覽器功能觸發。

能夠經過JavaScript在任什麼時候候觸發任意事件 => 在測試Web應用時特別有用

DOM3規範指明瞭模擬特定類型事件的方式。

DOM事件模擬

步驟:

  1. 使用document.createEvent()方法建立一個event對象。

    createEvent()方法接收一個參數,一個表示要建立事件類型的字符串。DOM2是英文複數形式,DOM3中是英文單數形式。可用值爲如下之一:

    • 」UIEvents「(DOM3是」UIEvent「):通用用戶界面事件(鼠標事件和鍵盤事件都繼承於此)
    • 」MouseEvents「(DOM3是」MouseEvent「):通用鼠標事件
    • 」HTMLEvents「(DOM3中無):通用HTML事件(已分散到其餘事件大類中)
    • 鍵盤事件(DOM3 Events中增長)
    • 「Events」:通用事件
    • 「CustomEvent」(DOM3中增長):自定義事件
  2. 使用事件相關的信息來初始化

    每種類型的event對象都有特定的方法,取決於調用createEvent()時傳入的參數

  3. 觸發事件

    事件目標調用dispatchEvent()方法。該方法存在於全部支持事件的DOM節點上。

    接收一個參數,即要觸發事件的event對象

  4. 冒泡並觸發事件處理程序執行

不一樣事件類型的模擬:

  1. 鼠標事件

    • 調用createEvent()並傳入」MouseEvents「參數
    • 調用返回的event對象的initMouseEvent()方法,爲新對象指定鼠標的特定信息。接收15個參數,分別對應鼠標事件會暴露的屬性,如type、bubbles、cancelable、view等,這四個是正確模擬事件惟一重要的幾個參數,由於瀏覽器要用到,其餘參數則是事件處理程序要用的。
    • event對象的target屬性會自動設置爲調用dispatchEvent()方法的節點

    全部鼠標事件均可以在DOM合規的瀏覽器中模擬出來

  2. 鍵盤事件

    • DOM3中建立鍵盤事件的方式是給createEvent()方法傳入參數」KeyboardEvent「
    • 調用返回的event對象的initKeyboardEvent()方法。接收8個參數,包括type、bubbles、cancelable、view等。

    DOM3 Events中廢棄了keypress事件,所以只能經過上述方式模擬keydown和keyup事件。

    在使用document.createEvent("KeyboardEvent")以前,最好檢測一下瀏覽器對DOM3鍵盤事件的支持狀況document.implementation.hasFeature("KeyboardEvents", "3.0")

    測試Chrome,調用initKeyboardEvent()方法傳入的key和modifier參數與在事件處理程序中打印出來的屬性不一致,可使用new KeyboardEvent()(參數與在事件處理程序中打印出的一致),另,兩種模擬都不會使文本框中有內容

    FF限定:

    • 給createEvent()傳入」KeyEvents「來建立鍵盤事件
    • 調用event對象的initKeyEvent()方法。接收10個參數,包括type、bubbles、cancelable、view等。

    測試:ff(88.0)顯示不支持 Uncaught DOMException: Operation is not supported

    其餘不支持鍵盤事件的瀏覽器:

    • 建立一個通用的事件,給createEvent()方法傳入參數」Events「
    • 調用event對象的initEvent()方法
    • 經過event.xxx方式指定特定於鍵盤的信息

    必須使用通用事件而不是用戶界面事件,由於用戶界面事件不容許直接給event對象添加屬性

  3. 其餘事件

    HTML事件:

    • 調用createEvent()方法並傳入「HTMLEvents」
    • 調用返回的event對象的initEvent()方法來初始化信息

    測試:模擬focus,能監聽到事件,可是沒有光標

  4. 自定義DOM事件

    DOM3新增自定義事件類型。不觸發原生DOM事件。

    • 調用createEvent()並傳入參數「CustomEvent」
    • 調用返回的event對象的initCustomEvent()方法,接收4個參數:type、bubbles、cancelable、detail
    • 調用dispatchEvent()

IE事件模擬

在IE8及更早版本中模擬事件。

步驟:

  1. 使用document對象的createEventObject()方法來建立event對象。不接收參數,返回一個通用event對象
  2. 手工給返回的對象指定但願該對象具有的全部屬性。(無初始化方法)可指定任何屬性,包括IE8及更早版本不支持的屬性。這些屬性值對於事件來講並不重要,只有事件處理程序纔會使用它們。
  3. 在事件目標上調用fireEvent()方法。接收2個參數:事件處理程序的名字和event對象。srcElement和type屬性會自動指派到event對象。

IE支持的全部事件均可以經過相同的方式來模擬。

小結

最多見的事件是在DOM3 Events規範或HTML5中定義的。

須要考慮內存與性能問題:

  • 限制頁面中事件處理程序數量。=> 避免佔用過多內存致使頁面響應慢,清理起來更方便

    • 使用事件委託
    • 在頁面卸載或元素從頁面刪除前,清理掉相關的事件處理程序
相關文章
相關標籤/搜索