基本事件模型


網頁應用程式本身就是事件驅動,透過使用者的操作或系統的事件,在適當的時候作些事情。在事件標準化之前,就存在於各瀏覽器的一個事件模型,稱為基本事件模型(Basic Event Model),雖然沒有標準化,但因相對(於 DOM Level 2 事件與 Internet Explorer 事件模型)來說,有比較跨瀏覽器的一致性,因此是大多數人最熟悉且最常用的事件模型。

在基本事件模型中,要在某個事件發生時,呼叫指定的函式,是將函式指定給某個特性。例如,要在網頁文件準備好,所有資源都載入後作些事情,可以註冊 windowload 事件,方式就是將函式指定給 window.onload 特性,這在之前的範例中看過:

window.onload = function() {
    // onload 事件發生時要作的事...
};

例如,要在按鈕的 click 事件發生時作些事,可以指定函式給按鈕元素的 onclick 特性:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
</head>
<body>

    <button id="btn1">按鈕一</button><br>
    <button id="btn2">按鈕二</button><br>
    <div id="console"></div>

<script type="text/javascript">
    function handler() {
        document.getElementById('console').innerHTML 
            = `Who's clicked: ${this.id}`;
    }
    document.getElementById('btn1').onclick = handler;
    document.getElementById('btn2').onclick = handler;
</script>

</body>
</html>

按我觀看結果

在上例中,用 handler 函式註冊了兩個按鈕的 click 事件,事件在元素上觸發而呼叫函式時,this 就會設定為當時觸發事件的元素。

在這個例子中,當你按下按鈕時而呼叫函式時,this 就參考至當時按下的按鈕。像以上的作法,稱之為傳統模型(Traditional model)或傳統註冊模型(Traditional registration model)。

儘管現在已不建議接下來的作法,但在過去開發的網頁程式中經常看到的作法就是,在標籤的屬性上撰寫 JavaScript 作為觸發事件時要執行的程式,這樣的作法稱之為行內模型(Inline model)或行內註冊模型(Inline registration model)。例如:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <script type="text/javascript">
        function handle(elem) {
            document.getElementById('console').innerHTML 
                = `Who's clicked: ${elem.id}`;
        }
    </script>  
</head>
<body>

    <button id="btn1" onclick="handle(this);">按鈕一</button><br>
    <button id="btn2" onclick="handle(this);">按鈕二</button><br>
    <div id="console"></div>

</body>
</html>

按我觀看結果

在標籤屬性上撰寫程式碼,儘管只有一小段,但仍算是在畫面中侵入程式碼,現在已不鼓勵這樣的作法。在上例中,並非直接指定 click 事件的處理器函式,事實上,在標籤上指定程式碼的作法,會自動建立匿名函式,也就是說,上例相當於:

document.getElementById('btn1').onclick = function() {
    handle(this);
};

你指定的程式碼,會成為匿名函式的本體內容,這也說明了上例中,this 其實就是指觸發事件時的元素。

有些事件觸發時會有預設的動作,例如表單的 submit 事件預設會發送表單,超鏈結的 click 事件預設會連結至指定網頁。在基本事件模型中,若要取消預設事件動作,可以讓事件處理器傳回 false。例如:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
</head>
<body>

    <form name="form1" action="fake.do">
        輸入資料:<input name="data"><br>
        <button type="submit">送出</button>
    </form>  

<script type="text/javascript">
  document.form1.onsubmit = function() {
      if(this.data.value.length === 0) {
          return false;
      }
  };
</script>  

</body>
</html>

按我觀看結果

在上例中,按下按鈕會觸發表單的 submit 事件,你檢查欄位是否有填值,沒有的話就在事件處理器中傳回 false,這會取消表單的發送,而這就是表單驗證的基本作法。若是使用行內模型。就會看到這種作法:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width">
    <script type="text/javascript">
        function validate(form) {
            if(form.data.value.length === 0) {
                return false;
            }
            return true;
        }
    </script>
</head>
<body>

    <form name="form1" action="fake.do" onsubmit="return validate(this);">
        輸入資料:<input name="data"><br>
        <button type="submit">送出</button>
    </form>    

</body>
</html>

按此觀看成果

要在使用者離開頁面時,做一個簡單的確認,若使用行內模型,可以如下:

<a href="http://caterpillar.onlyfun.net" 
   onclick="return confirm('要離開了嗎?');">首頁</a>

事件不一定要由使用者的操作觸發,也可以直接呼叫方法來觸發事件。例如可以呼叫表單元素的 submit 方法,如果在載入頁面時,想要讓第一個輸入欄位取得焦點,可以呼叫輸入欄位元素的 focus 方法等。

例如,如果某個輸入欄位 iduser,則可以如下在頁面資源全部載入後,讓該欄位取得焦點:

window.onload = function() {
    document.getElementById('test').focus();
};

如果你使用 ES6 的箭號函式來做為事件處理器,必須特別注意的是,箭號函式的 this 是根據語彙環境,而不是像 function 根據呼叫者是誰來決定。