2025-02-27 日报 Day110

2025-02-27 日报 Day110

Yuyang 前端小白🥬

今日的鸡汤

辞暮尔尔,
烟火年年。

今日学习内容

1、JS 红皮书 P490-504 第十七章:事件

今日笔记

1、JavaScript 与 HTML 的交互是通过事件实现的,事件代表文档或浏览器窗口中某个有意义的时刻。可以使用仅在事件发生时执行的监听器(也叫处理程序)订阅事件。在传统软件工程领域,这个模型叫“观察者模式”,其能够做到页面行为(在 JavaScript 中定义)与页面展示(在 HTML 和 CSS 中定义)的分离。
2、事件流: 事件流描述了页面接收事件的顺序。

  • 事件冒泡: IE 事件流被称为事件冒泡,这是因为事件被定义为从最具体的元素(文档树中最深的节点)开始触发,然后向上传播至没有那么具体的元素(文档)。比如有如下 HTML 页面:
1
2
3
4
5
6
7
8
9
<!DOCTYPE html>
<html>
<head>
<title>Event Bubbling Example</title>
</head>
<body>
<div id="myDiv">Click Me</div>
</body>
</html>

在点击页面中的

元素后,click 事件会以如下顺序发生
(1)

(2)
(3)
(4) document
也就是说,
元素,即被点击的元素,最先触发 click 事件。然后,click 事件沿 DOM 树一路向上,在经过的每个节点上依次触发,直至到达 document 对象。所有现代浏览器都支持事件冒泡,现代浏览器中的事件会一直冒泡到 window 对象。

  • 事件捕获: 事件捕获的意思是最不具体的节点应该最先收到事件,而最具体的节点应该最后收到事件。事件捕获实际上是为了在事件到达最终目标前拦截事件。如果前面的例子使用事件捕获,则点击
    元素会以下列顺序触发 click 事件:
    (1) document
    (2)
    (3)
    (4)

    在事件捕获中,click 事件首先由 document 元素捕获,然后沿 DOM 树依次向下传播,直至到达实际的目标元素

    3、DOM 事件流: DOM2 Events 规范规定事件流分为 3 个阶段:事件捕获、到达目标和事件冒泡。事件捕获最先发生,为提前拦截事件提供了可能。然后,实际的目标元素接收到事件。最后一个阶段是冒泡,最迟要在这个阶段响应事件。
    在 DOM 事件流中,实际的目标(
    元素)在捕获阶段不会接收到事件。这是因为捕获阶段从 document 到再到就结束了。下一阶段,即会在
    元素上触发事件的“到达目标”阶段,通常在事件处理时被认为是冒泡阶段的一部分(稍后讨论)。然后,冒泡阶段开始,事件反向传播至文档。
    4、事件处理程序: 事件意味着用户或浏览器执行的某种动作。比如,单击(click)、加载(load)、鼠标悬停(mouseover)。为响应事件而调用的函数被称为事件处理程序(或事件监听器)。事件处理程序的名字以”on”开头,因此 click 事件的处理程序叫作 onclick,而 load 事件的处理程序叫作 onload。有很多方式可以指定事件处理程序。
  • HTML 事件处理程序: 使用 HTML 指定事件处理程序的最后一个问题是 HTML 与 JavaScript 强耦合。如果需要修改事件处理程序,则必须在两个地方,即 HTML 和 JavaScript 中,修改代码。这也是很多开发者不使用 HTML 事件处理程序,而使用 JavaScript 指定事件处理程序的主要原因。
  • DOM0 事件处理程序: 像这样使用 DOM0 方式为事件处理程序赋值时,所赋函数被视为元素的方法。因此,事件处理程序会在元素的作用域中运行,即 this 等于元素。下面的例子演示了使用 this 引用元素本身:
1
2
3
4
let btn = document.getElementById("myBtn");
btn.onclick = function () {
console.log(this.id); // "myBtn"
};

通过将事件处理程序属性的值设置为 null,可以移除通过 DOM0 方式添加的事件处理程序,如下面的例子所示:

1
btn.onclick = null; // 移除事件处理程序
  • DOM2 事件处理程序: DOM2 Events 为事件处理程序的赋值和移除定义了两个方法:addEventListener()和 removeEventListener()。这两个方法暴露在所有 DOM 节点上,它们接收 3 个参数:事件名、事件处理函数和一个布尔值,true 表示在捕获阶段调用事件处理程序,false(默认值)表示在冒泡阶段调用事件处理程序。
1
2
3
4
5
6
7
8
let btn = document.getElementById("myBtn");
btn.addEventListener(
"click",
() => {
console.log(this.id);
},
false
);

以上代码为按钮添加了会在事件冒泡阶段触发的 onclick 事件处理程序(因为最后一个参数值为 false)。使用 DOM2 方式的主要优势是可以为同一个事件添加多个事件处理程序。来看下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let btn = document.getElementById("myBtn");
btn.addEventListener(
"click",
() => {
console.log(this.id);
},
false
);
btn.addEventListener(
"click",
() => {
console.log("Hello world!");
},
false
);

通过 addEventListener()添加的事件处理程序只能使用 removeEventListener()并传入与添加时同样的参数来移除。这意味着使用 addEventListener()添加的匿名函数无法移除,如下面的例子所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let btn = document.getElementById("myBtn");
btn.addEventListener(
"click",
() => {
console.log(this.id);
},
false
);
// 其他代码
btn.removeEventListener(
"click",
function () {
// 没有效果!
console.log(this.id);
},
false
);

这个例子通过 addEventListener()添加了一个匿名函数作为事件处理程序。然后,又以看起来相同的参数调用了 removeEventListener()。但实际上,第二个参数与传给 addEventListener()的完全不是一回事。传给 removeEventListener()的事件处理函数必须与传给 addEventListener()的是同一个,如下面的例子所示:

1
2
3
4
5
6
7
let btn = document.getElementById("myBtn");
let handler = function () {
console.log(this.id);
};
btn.addEventListener("click", handler, false);
// 其他代码
btn.removeEventListener("click", handler, false); // 有效果!

这个例子有效,因为调用 addEventListener()和 removeEventListener()时传入的是同一个函数。
大多数情况下,事件处理程序会被添加到事件流的冒泡阶段,主要原因是跨浏览器兼容性好。把事件处理程序注册到捕获阶段通常用于在事件到达其指定目标之前拦截事件。如果不需要拦截,则不要使用事件捕获。

  • IE 事件处理程序: IE 实现了与 DOM 类似的方法,即 attachEvent()和 detachEvent()。这两个方法接收两个同样的参数:事件处理程序的名字和事件处理函数。因为 IE8 及更早版本只支持事件冒泡,所以使用 attachEvent()添加的事件处理程序会添加到冒泡阶段。
  • 跨浏览器事件处理程序: 为了以跨浏览器兼容的方式处理事件,很多开发者会选择使用一个 JavaScript 库,其中抽象了不同浏览器的差异。要确保事件处理代码具有最大兼容性,只需要让代码在冒泡阶段运行即可。以下就是包含这两个方法的 EventUtil 对象:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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 {
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 {
element["on" + type] = null;
}
},
};

let btn = document.getElementById("myBtn")
let handler = function() {
console.log("Clicked");
};
EventUtil.addHandler(btn, "click", handler);
// 其他代码
EventUtil.removeHandler(btn, "click", handler);

5、事件对象: 在 DOM 中发生事件时,所有相关信息都会被收集并存储在一个名为 event 的对象中。这个对象包含了一些基本信息,比如导致事件的元素、发生的事件类型,以及可能与特定事件相关的任何其他数据。例如,鼠标操作导致的事件会生成鼠标位置信息,而键盘操作导致的事件会生成与被按下的键有关的信息。所有浏览器都支持这个 event 对象,尽管支持方式不同。

  • DOM事件对象: 在 DOM 合规的浏览器中,event 对象是传给事件处理程序的唯一参数。不管以哪种方式(DOM0或 DOM2)指定事件处理程序,都会传入这个 event 对象。下面的例子展示了在两种方式下都可以使用事件对象:
    1
    2
    3
    4
    5
    6
    7
    let btn = document.getElementById("myBtn"); 
    btn.onclick = function(event) {
    console.log(event.type); // "click"
    };
    btn.addEventListener("click", (event) => {
    console.log(event.type); // "click"
    }, false);
    如前所述,事件对象包含与特定事件相关的属性和方法。不同的事件生成的事件对象也会包含不同的属性和方法。不过,所有事件对象都会包含下表列出的这些公共属性和方法。
属性/方法 类 型 读/写 说 明
bubbles 布尔值 只读 表示事件是否冒泡
cancelable 布尔值 只读 表示是否可以取消事件的默认行为
currentTarget 元素 只读 当前事件处理程序所在的元素
defaultPrevented 布尔值 只读 true 表示已经调用 preventDefault()方法(DOM3 Events 中新增)
detail 整数 只读 事件相关的其他信息
eventPhase 整数 只读 表示调用事件处理程序的阶段:1 代表捕获阶段,2 代表到达目标,3 代表冒泡阶段
preventDefault() 函数 只读 用于取消事件的默认行为。只有 cancelable 为 true 才可以调用这个方法
stopImmediatePropagation() 函数 只读 用于取消所有后续事件捕获或事件冒泡,并阻止调用任何后续事件处理程序(DOM3 Events 中新增)
stopPropagation() 函数 只读 用于取消事件的冒泡。只有 bubbles 为 true 才可以调用这个方法
target 元素 只读 事件目标
trusted 布尔值 只读 true 表示事件是由浏览器生成的。false 表示事件是开
type 字符串 只读 被触发的事件类型,比如 click、load、mouseover
View AbstractView 只读 与事件相关的抽象视图。等于事件所发生的 window 对象
在事件处理程序内部,this 对象始终等于 currentTarget 的值,而 target 只包含事件的实际目标。如果事件处理程序直接添加在了意图的目标,则 this、currentTarget 和 target 的值是一样的。下面的例子展示了这两个属性都等于 this 的情形:
1
2
3
4
5
let btn = document.getElementById("myBtn"); 
btn.onclick = function(event) {
console.log(event.currentTarget === this); // true
console.log(event.target === this); // true
};

如果这个事件处理程序是添加到按钮的父节点(如 document.body)上,那么它们的值就不一样了。比如下面的例子在 document.body 上添加了单击处理程序:

1
2
3
4
5
document.body.onclick = function(event) { 
console.log(event.currentTarget === document.body); // true
console.log(this === document.body); // true
console.log(event.target === document.getElementById("myBtn")); // true
};

这种情况下点击按钮,this 和 currentTarget 都等于 document.body,这是因为它是注册事件处理程序的元素。而 target 属性等于按钮本身,这是因为那才是 click 事件真正的目标。由于按钮本身并没有注册事件处理程序,因此 click 事件冒泡到 document.body,从而触发了在它上面注册的处理程序。
type 属性在一个处理程序处理多个事件时很有用。比如下面的处理程序中就使用了 event.type:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let btn = document.getElementById("myBtn"); 
let handler = function(event) {
switch(event.type) {
case "click":
console.log("Clicked");
break;
case "mouseover":
event.target.style.backgroundColor = "red";
break;
case "mouseout":
event.target.style.backgroundColor = "";
break;
}
};
btn.onclick = handler;
btn.onmouseover = handler;
btn.onmouseout = handler;

preventDefault()方法用于阻止特定事件的默认动作。比如,链接的默认行为就是在被单击时导航到 href 属性指定的 URL。如果想阻止这个导航行为,可以在 onclick 事件处理程序中取消,如下面的例子所示:

1
2
3
4
let link = document.getElementById("myLink"); 
link.onclick = function(event) {
event.preventDefault();
};

任何可以通过 preventDefault()取消默认行为的事件,其事件对象的 cancelable 属性都会设置为 true。
stopPropagation()方法用于立即阻止事件流在 DOM 结构中传播,取消后续的事件捕获或冒泡。例如,直接添加到按钮的事件处理程序中调用 stopPropagation(),可以阻止 document.body 上注册的事件处理程序执行。比如:

1
2
3
4
5
6
7
8
let btn = document.getElementById("myBtn"); 
btn.onclick = function(event) {
console.log("Clicked");
event.stopPropagation();
};
document.body.onclick = function(event) {
console.log("Body clicked");
};

eventPhase 属性可用于确定事件流当前所处的阶段。如果事件处理程序在捕获阶段被调用,则eventPhase 等于 1;如果事件处理程序在目标上被调用,则 eventPhase 等于 2;如果事件处理程序在冒泡阶段被调用,则 eventPhase 等于 3。不过要注意的是,虽然“到达目标”是在冒泡阶段发生的,但其 eventPhase 仍然等于 2。
注意 event 对象只在事件处理程序执行期间存在,一旦执行完毕,就会被销毁。

此页目录
2025-02-27 日报 Day110