基本事件模型


网页应用程序本身就是事件驱动,透过使用者的操作或系统的事件,在适当的时候作些事情。在事件标准化之前,就存在于各浏览器的一个事件模型,称为基本事件模型(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根据调用者是谁来决定。


展开阅读全文