跳转到内容
seren-spark

22 事件监听

DOM 允许我们书写 JavaScript 代码以让 HTML 元素作出反应。

什么是 “事件”:用户与网页的交互动作。

“监听” 顾名思义,就是让计算机随时能够发现这个事件发生了,从而执行程序员预先编写好的一些程序。

设置事件监听的方法主要有 onxxxaddEventListener() 两种,二者的区别将在 “事件传播” 一课中介绍。

原始的事件处理方法:“直接通过事件绑定函数”

比如:

HTML:<button onclick="add();">点击</button>

JS:function add() { alert("相加"); }

以上方式不推荐使用!!!

浏览器的事件模型,就是通过监听函数(listener)对事件做出反应。事件发生后,浏览器监听到了这个事件,就会执行对应的监听函数。这是事件驱动编程模式(event-driven)的主要编程方式。

JavaScript 有三种方法,可以为事件绑定监听函数。

HTML 语言允许在元素的属性中,直接定义某些事件的监听代码。

<body onload="doSomething()">
<div onclick="console.log('触发事件')">

上面代码为body节点的load事件、div节点的click事件,指定了监听代码。一旦事件发生,就会执行这段代码。

元素的事件监听属性,都是on加上事件名,比如onload就是on + load,表示load事件的监听代码。

注意,这些属性的值是将会执行的代码,而不是一个函数。

<!-- 正确 -->
<body onload="doSomething()">

<!-- 错误 -->
<body onload="doSomething">

一旦指定的事件发生,on-属性的值是原样传入 JavaScript 引擎执行。因此如果要执行函数,不要忘记加上一对圆括号。

使用这个方法指定的监听代码,只会在冒泡阶段触发。

<div onclick="console.log(2)">
  <button onclick="console.log(1)">点击</button>
</div>

上面代码中,<button><div>的子元素。<button>click事件,也会触发<div>click事件。由于on-属性的监听代码,只在冒泡阶段触发,所以点击结果是先输出1,再输出2,即事件从子元素开始冒泡到父元素。

直接设置on-属性,与通过元素节点的setAttribute方法设置on-属性,效果是一样的。

el.setAttribute('onclick', 'doSomething()');
// 等同于
// <Element onclick="doSomething()">

元素节点对象的事件属性,同样可以指定监听函数。

window.onload = doSomething;

div.onclick = function (event) {
  console.log('触发事件');
};

使用这个方法指定的监听函数,也是只会在冒泡阶段触发。

注意,这种方法与 HTML 的on-属性的差异是,它的值是函数名(doSomething),而不像后者,必须给出完整的监听代码(doSomething())。

所有 DOM 节点实例都有addEventListener方法,用来为该节点定义事件的监听函数。

window.addEventListener('load', doSomething, false);

addEventListener方法的详细介绍,参见下一个大标题。

上面三种方法,第一种“HTML 的 on- 属性”,违反了 HTML 与 JavaScript 代码相分离的原则,将两者写在一起,不利于代码分工,因此不推荐使用。

第二种“元素节点的事件属性”的缺点在于,同一个事件只能定义一个监听函数,也就是说,如果定义两次onclick属性,后一次定义会覆盖前一次。因此,也不推荐使用。

第三种EventTarget.addEventListener是推荐的指定监听函数的方法。它有如下优点:

  • 同一个事件可以添加多个监听函数。
  • 能够指定在哪个阶段(捕获阶段还是冒泡阶段)触发监听函数。
  • 除了 DOM 节点,其他对象(比如windowXMLHttpRequest等)也有这个接口,它等于是整个 JavaScript 统一的监听函数接口。

监听函数内部的this指向触发事件的那个元素节点。

<button id="btn" onclick="console.log(this.id)">点击</button>

执行上面代码,点击后会输出btn

其他两种监听函数的写法,this的指向也是如此。

// HTML 代码如下
// <button id="btn">点击</button>
var btn = document.getElementById('btn');

// 写法一
btn.onclick = function () {
  console.log(this.id);
};

// 写法二
btn.addEventListener(
  'click',
  function (e) {
    console.log(this.id);
  },
  false
);

上面两种写法,点击按钮以后也是输出btn

3.使用addEventListener方法添加监听

Section titled “3.使用addEventListener方法添加监听”

DOM 节点的事件操作(监听和触发),都定义在EventTarget接口。所有节点对象都部署了这个接口,其他一些需要事件通信的浏览器内置对象(比如,XMLHttpRequestAudioNodeAudioContext)也部署了这个接口。

该接口主要提供三个实例方法。

  • addEventListener():绑定事件的监听函数
  • removeEventListener():移除事件的监听函数
  • dispatchEvent():触发事件

addEventListener 是 DOM 对象专门用来添加事件监听的方法,它的两个参数分别为【事件类型】和【事件回调】。

image-20220825151249654

EventTarget.addEventListener()用于在当前节点或对象上(即部署了 EventTarget 接口的对象),定义一个特定事件的监听函数。一旦这个事件发生,就会执行监听函数。该方法没有返回值。

target.addEventListener(type, listener[, useCapture]);

该方法接受三个参数。

  • type:事件名称,大小写敏感。
  • listener:监听函数。事件发生时,会调用该监听函数。
  • useCapture:布尔值,如果设为true,表示监听函数将在捕获阶段(capture)触发(后文事件的传播 讲解)。该参数可选,默认值为false(监听函数只在冒泡阶段被触发)。

下面是一个例子。

function hello() {
  console.log('Hello world');
}

var button = document.getElementById('btn');
button.addEventListener('click', hello, false);

上面代码中,button节点的addEventListener()方法绑定click事件的监听函数hello(),该函数只在冒泡阶段触发。

关于参数,有两个地方需要注意。

首先,第二个参数除了监听函数,还可以是一个具有handleEvent方法的对象,效果与监听函数一样。

buttonElement.addEventListener('click', {
  handleEvent: function (event) {
    console.log('click');
  }
});

上面代码中,addEventListener()方法的第二个参数,就是一个具有handleEvent()方法的对象。

完成事件监听分成3个步骤:

  1. 获取 DOM 元素
  2. 通过 addEventListener 方法为 DOM 节点添加事件监听
  3. 等待事件触发,如用户点击了某个按钮时便会触发 click 事件类型
  4. 事件触发后,相对应的回调函数会被执行

大白话描述:所谓的事件无非就是找个机会(事件触发)调用一个函数(回调函数)。

EventTarget.removeEventListener()方法用来移除addEventListener()方法添加的事件监听函数。该方法没有返回值。

div.addEventListener('click', listener, false);
div.removeEventListener('click', listener, false);

removeEventListener()方法的参数,与addEventListener()方法完全一致。它的第一个参数“事件类型”,大小写敏感。

注意,removeEventListener()方法移除的监听函数,必须是addEventListener()方法添加的那个监听函数,而且必须在同一个元素节点,否则无效。

div.addEventListener('click', function (e) {}, false);
div.removeEventListener('click', function (e) {}, false);

上面代码中,removeEventListener()方法无效,因为监听函数不是同一个匿名函数。

element.addEventListener('mousedown', handleMouseDown, true);
element.removeEventListener("mousedown", handleMouseDown, false);

上面代码中,removeEventListener()方法也是无效的,因为第三个参数不一样。

EventTarget.dispatchEvent()方法在当前节点上触发指定事件,从而触发监听函数的执行。该方法返回一个布尔值,只要有一个监听函数调用了Event.preventDefault(),则返回值为false,否则为true

target.dispatchEvent(event)

dispatchEvent()方法的参数是一个Event对象的实例(详见《Event 对象》章节)。

para.addEventListener('click', hello, false);
var event = new Event('click');
para.dispatchEvent(event);

上面代码在当前节点触发了click事件。

如果dispatchEvent()方法的参数为空,或者不是一个有效的事件对象,将报错。

下面代码根据dispatchEvent()方法的返回值,判断事件是否被取消了。

var canceled = !cb.dispatchEvent(event);
if (canceled) {
  console.log('事件取消');
} else {
  console.log('事件未取消');
}
事件名事件描述
onclick当鼠标单击某个对象
ondblclick当鼠标双击某个对象
onmousedown当某个鼠标按键在某个对象上被按下
onmouseup当某个鼠标按键在某个对象上被松开
onmousemove当某个鼠标按键在某个对象上被移动
onmouseenter当鼠标进入某个对象(相似事件 onmouseover
onmouseleave当鼠标离开某个对象(相似事件 onmouseout
事件名事件描述
onkeypress当某个键盘的键被按下(系统按钮如箭头键和功能键无法得到识别)
onkeydown当某个键盘的键被按下(系统按钮可以识别,并且会先于 onkeypress 发生)
onkeyup当某个键盘的键被松开
事件名事件描述
onchange当用户改变完成域的内容
oninput当用户输入的时候
onfocus当某元素获得焦点(比如 tab 键或鼠标点击)
onblur当某元素失去焦点
onsubmit当表单被提交
onreset当表单被重置
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <form id="myform">
        <p>
            姓名:
            <input type="text" name="nameField">
        </p>
        <p>
            年龄:
            <input type="text" name="ageField">
        </p>
        <p>
            <input type="submit">
        </p>
    </form>

    <script>
        var myform = document.getElementById('myform');
        // 表单对象可以通过 “打点”name 属性,得到里面的子元素。
        var nameField = myform.nameField;
        var ageField = myform.ageField;

        nameField.onchange = function () {
            console.log('你已经修改完了姓名');
        };

        nameField.oninput = function () {
            console.log('你正在修改姓名');
        };

        nameField.onfocus = function () {
            console.log('姓名框已经得到了焦点');
        }

        nameField.onblur = function () {
            console.log('姓名框已经失去了焦点');
        }

        myform.onsubmit = function () {
            alert('你正在尝试提交表单');
         /* return true;  可以省略,默认执行表单提交 */
         /* return false; 不执行表单提交,比如用户信息填写不全就不应该提交表单 */
        }
    </script>
</body>

</html>

表单对象可以通过 “打点” name 属性,得到里面的子元素。

事件名事件描述
onload当页面或图像被完成加载
onunload当用户退出页面