【前端基础】2 - 4 事件

§2-4 事件

2-4.1 事件简述

事件是发生在当前编程系统中的事情。当某个事件发生时,系统会产生该事件的 “信号”,并提供一系列可用于处理事件的机制。在浏览器中,事件由窗口触发,且可分为多种不同类型。

常见的事件有:

  • 用户选择、点击或鼠标悬停于某个元素之上。
  • 用户按下了键盘上的某个按键。
  • 用户调整浏览器窗口大小或关闭浏览器窗口。
  • 网页加载结束。
  • 提交表单。
  • 视频播放、暂停、结束。
  • 发生错误。

为了使得网页或程序能够对事件作出回应,我们需要添加事件处理器(或事件监听器)。事件处理器会同时监听网页中发生的事件,并处理这些事件。事件处理器本质上是一个 JavaScript 函数,我们需要将这个函数注册为事件处理器。

2-4.2 addEventListener() 添加事件处理器

在为网页中的元素添加事件处理器前,整个网页并不具备可用的交互功能。以下列的 HTML 文档为例,按钮在被点击时,并不触发任何效果。

<body>
    <button>
        点击我
    </button>
</body>

为了让按钮能够在被点击时响应该事件,我们需要为按钮添加点击事件的监听器。一般地,我们使用 addEventListener() 为元素添加事件监听

// 获取按钮 DOM 对象
const btn = document.querySelector('button');
// 定时器 id
let interval;
// 按钮禁用倒计时
let cd;

// addEventListener() 添加事件监听
/*
	其中一种用法:addEventListener(type, listener);
	type		 - 字符串,点击事件类型,所有可用的事件类型详见事件参考
	listener	 - 函数,事件被触发时
*/
btn.addEventListener('click', () => {
    // 设置按钮禁用 CD
    cd = 5;
    // 禁用按钮
    btn.disabled = true;
    btn.innerHTML = `Take a break (${cd})`;
    
    // 设置定时器
    interval = setInterval(() => {
        // 倒计时
        cd--;
        if (cd == 0) {
            // 恢复按钮
            btn.innerHTML = 'Click me';
            btn.disabled = false;
            // 取消计时器
            clearInterval(interval);
        }
        else {
            // 更新按钮
            btn.innerHTML = `Take a break (${cd})`;
        }
    }, 1000);
});

这样,每当按钮被按下时,按钮会进入 5 秒的禁用状态,随即恢复原状。

此外,对同一个对象,可以反复调用 addEventListener() 为元素添加更多的事件处理器,使得元素能够针对多种不同的事件作出反应。

btn.addEventListener("click", fn1);
btn.addEventListener("click", fn2);
btn.addEventListener("focus", fn3);

其他添加事件处理器的方法:一般而言,我们首选使用 addEventListener() 方法为元素添加事件处理器,它支持多事件监听。

  • 添加事件处理器属性:触发事件的对象具有事件处理器属性,一般为 onXXX,其中 XXX 为事件类型。但这种方法只能为元素添加针对一个事件的一个处理机制。尝试反复调用该属性修改事件监听会导致新事件处理器覆盖旧的事件处理器。

    btn.onclick = () => {
        // code here
    }
    
  • 内联事件处理器:在 HTML 文档中为元素声明内联事件处理器。这种方式最好不要使用,一方面事件处理数量受限,一方面在大型复杂的页面中,这会导致 HTML 与 JavaScript 代码混合而难以维护。

    <button onclick="...">Click me</button>
    

因此,我们一般使用 addEventListener() 更便于操作,且能够保证 HTML 与 JavaScript 分离,易于维护和复用。

若不想再使用某一事件处理器,可以使用 removeEventListener() 移除事件处理器

btn.removeEventListener(type, callbackfn);

上述 addEventListener() 的调用是一种双参数的调用。实际上,该方法还有其他重载:

addEventListener(type, listener);
addEventListener(type, listener, options);
addEventListener(type, listener, useCapture);

第三个参数赋予了事件处理器一定的控制能力。以 options 为例,它是一个指定有关 listener 的可选参数对象:

  • capture(可选):布尔值,表示 listener 会在该类型事件的捕获阶段传播到该 EventTarget 时触发;
  • once(可选):布尔值,表示 listener 在被添加之后最多只能调用一次;
  • passive(可选):布尔值,设为 true 时,表示 listener 永远不会调用 preventDefault()
  • signal(可选):AbortSignal,该 AbortSignalabort() 方法被调用时,监听器被移除;

为元素添加事件处理器的一个重要因素在于明确事件类型,更详细的事件类型参考,详见 MDN(见底部链接)。

2-4.3 事件对象 Event

一件事情被触发时,会产生一个事件对象。事件对象提供了事件产生的有关信息。一般地,事件对象存储了一些有关事件的通用信息,如产生事件的元素的引用 target。我们可以向事件处理函数中提供一个参数,eevtevent),该参数即为事件对象,它将会自动传递给事件处理函数。

例如,我们希望按钮在被点击一次之后被禁用,且修改其颜色。

btn.addEventListener("click", (e) => {
    e.target.disabled = 'true';
    e.target.style.backgroundColor = '#333';
    e.target.style.color = '#aaa';
});

事件对象的接口为 Event,该接口定义了事件对象的一般属性和方法。但一些特殊的事件对象添加了与该类型事件相关的额外属性,因此,这些事件对象的接口并不是直接来自于 Event。如与键盘事件有关的事件对象为 KeyboardEvent,这是一个专门的 Event 对象。

以前文的按钮示例为例,若我们希望在网页中按下 Enter 时,触发按钮被点击的效果,我们将点击事件的处理函数抽取为 click(),将代码修改如下:

const btn = document.querySelector('button');
const body = document.querySelector('body');

const click = function () { /* 代码省略 */};

btn.addEventListener("click", click);
body.addEventListener("keyup", (event) => {
    // 通过事件对象获取按下的按键
    if (event.code === 'Enter') {
        click();
    }
})

获取事件对象:在事件处理器的事件处理函数中添加参数 e,该函数在被调用时就会被传入事件对象。

2-4.4 阻止默认行为

一些元素会在指定事件发生时触发其默认行为。这一情况的常见例子是 Web 表单,当用户点击提交按钮时,我们希望先检查用户在表单中填写的数据是否满足要求,当且仅当用户所填数据满足要求时提交数据。然而实际上,用户所填的数据往往并不会完全正确,在这种情况下,我们希望发现数据错误时终止数据提交。

提交表单所触发的事件是提交事件(submit),调用事件对象的 preventDefault() 方法以阻止默认行为。

<body>
    <form action="..." method="post" autocomplete="on" class="example">
        <div class="form-content">
            <label for="first-name">First name: </label>
        <input type="text" name="first-name" id="first-name" required/>
        </div>
        <div class="form-content">
            <label for="last-name">Last name: </label>
            <input type="text" name="last-name" id="last-name" required/>
        </div>
        <div class="form-content">
            <input type="submit"/>
        </div>
    </form>
    <p></p>
</body>
form.example {
    display: table;
}

form.example .form-content {
    display: table-row;
}

label,
input {
    display: table-cell;
}

label {
    padding-right: 10px;
}

这个表单通过为表单项添加属性 required 确保字段不为空,但这只是最初级的表单验证。我们希望所提交的数据合法。

这里以姓名不能为空白串为例,实现一个非常弱的表单验证。

const form = document.querySelector('form.example');
const fname = document.querySelector('#first-name');
const lname = document.querySelector('#last-name');
const p = document.querySelector('p');

form.addEventListener('submit' (e) => {
    if (fname.value.trim().length === 0 || lname.value.trim().length === 0) {
        e.preventDefault();
        p.textContent = 'Invalid name.';
    }
});

2-4.5 事件冒泡与事件捕获

事件冒泡用于处理嵌套元素的事件。

考虑下列的嵌套结构:

<body class="body">
    <div class="container">
        <div class="content"></div>
        <div class="content"></div>
        <div class="content"></div>
    </div>
</body>

.container 作为父元素,内含三个 .content 子元素。这里,我们让父元素注册一个点击事件处理器:

const container = document.querySelector('.container');
const click_handler = () => {
    console.log(`你点击了 ${Array.from(e.currentTarget.classList)[0]} 元素。`);
};

container.addEventListener('click', click_handler);

我们让父元素使用弹性布局,其中的三个子元素等比分配空间(CSS 代码省略),并让三个子元素具有不同的颜色以便区分。点击三个子元素的任意一个,控制台都会输出:

你点击了 container 元素。

可以看到,父元素上触发了单击事件。若此时,我们将所有元素(包含 <body>)都添加上相同的事件处理器,重复相同的触发操作,会得到:

const body = document.querySelector('body');
const container = document.querySelector('.container');
const content = document.querySelector('.content');

const click_handler = (e) => {
    console.log(`你点击了 ${Array.from(e.currentTarget.classList)[0]} 元素。`);
}

body.addEventListener('click', click_handler);
container.addEventListener('click', click_handler);
content.addEventListener('click', click_handler);
你点击了 content 元素。
你点击了 container 元素。
你点击了 body 元素。

可见,对于相同的事件类型,事件冒泡的顺序是:从最先触发事件的嵌套最深元素开始,逐级向上传递至父元素。即事件会从最里面触发的元素冒泡而出。

若运用得当,事件冒泡会为我们节省重复代码,提高效率,这也称为事件委托。但有时,事件冒泡也可能会为我们带来意外的结果。为防止事件在向上冒泡的过程中,由某一级父元素事件处理器意外/错误处理,我们可以使用 stopPropagation() 方法阻止事件冒泡。

事件委托的一个反向版本是事件捕获。这需要在为元素添加事件处理器时传递一个 capture: trueoptions 对象:

content.addEventListener('click', click_handler);
container.addEventListener('click', click_handler, { capture: true });
body.addEventListener('click', click_handler, { capture: true });

复现同样的触发操作,会得到与事件冒泡相反的顺序:

你点击了 body 元素。
你点击了 container 元素。
你点击了 content 元素。

默认情况下,几乎所有事件处理程序都是在冒泡阶段注册,者在大多数情况下更有意义。

2-4.6 常用事件

下表列出了常用的事件类型。

事件 说明
click 鼠标点击事件
mouseenter 鼠标进入元素
mouseleave 鼠标离开元素
mouseover 鼠标悬停于元素之上
mouseout 鼠标离开元素
scroll 页面滚动
focus 获得焦点
blur 失去焦点
input 键盘输入
keydown, keyup 键盘按下/弹起
resize 调整浏览器窗口尺寸
load 外部资源加载完成
DOMContentLoaded 文档加载完毕,解析完成
touchstart 开始触摸
touchend 触摸停止
touchmove 触摸滑动

2-4.X 外部链接

Introduction to events - Learn web development | MDN (mozilla.org)

Event reference | MDN (mozilla.org)

posted @ 2024-03-09 21:20  Zebt  阅读(8)  评论(0编辑  收藏  举报