JavaScript中的事件

1 事件(Event)

事件也就是用户或者浏览器执行的某种动作,而JS与Html之间的交互是通过事件而来的。使用仅在事件发生时执行的**监听器(事件处理程序)**来订阅事件。web浏览器可以发生多种事件,在DOM3 Events定义了如下事件类型:

  • 用户界面事件(UIEvent):涉及与BOM交互的通用浏览器事件
  • 焦点事件(FocusEvent):在元素获得和失去焦点时触发
  • 鼠标事件(MouseEvent)
  • 滚轮事件(WheelEvent):使用鼠标滚轮时触发
  • 输入事件(InputEvent)
  • 键盘事件(KeyboardEvent)
  • 合事事件(CompositionEvent):在使用某种IME(Input Method Editor,输入法编辑器)输入字符时触发

1.1 事件处理程序

事件处理器(或事件监听器),就是来响应某个事件的函数。主要有HTML事件处理程序、DOM0事件处理程序和DOM2事件处理程序。

1.1.1 HTML事件处理程序

也就是直接将事件函数写在HTML元素的属性上,而所要执行的函数代码,实际上是通过JS引擎由eval()调用的,它是全局作用域。举个例子:

<button onclick="alert('hello,world')"></button>

同时它也可以写成这样:

<button onclick="printHello()"></button>
<script>
	function printHello() {
		alert(this)//object window
		alert('hello,world');
	}
</script>

HTML事件处理程序有一定的缺点:

  1. 时机问题:比如当<script>中的js代码还没加载,就提前执行事件处理程序,会造成错误
  2. js与html强耦合:如果修改事件处理器,需要同时改js和html两个地方的代码
  3. 对事件处理程序作用链的扩展在不同浏览器中可能导致不同的结果

针对这些缺点,在实际的代码编写中,更多人选择JavaScript指定事件处理程序

1.1.2 DOM0事件处理程序

在传统的监听器处理方式中,一般把监听器赋值给DOM元素的一个事件处理程序属性(每个元素都有,比如onclick、onload等等),完全通过js代码来进行处理,这个时候btn是局部作用域,而且js和html代码也进行解耦,如下代码:

<button id="button">
<script>
    let btn = document.getElementById("button");
    btn.onclick=function(){
			alert(this);//object HTMLButtonElement
			alert('test DOM0 event function');
		}
</script>

但是同样也存在着一些问题:

  • 无法为一个onclick属性添加多个事件处理程序
  • 对于一个元素的事件处理程序属性,也无法轻易赋值事件处理程序(有可能会覆盖之前其他的事件处理程序)

所以针对这一点,DOM2做了一些相关的优化

1.1.3 DOM2事件处理程序

DOM2 Events为事件处理程序的增加了两个方法,这些方法暴露在所有的DOM节点上

  • addEventListener():事件处理程序赋值
  • removeEventListener():事件处理程序移除

此外它们接收三个参数:

  • 事件名:不同的事件的名称
  • 事件处理函数:事件处理程序实现
  • 一个布尔值:
    • true:代表捕获阶段调用事件处理程序
    • false:表示在冒泡阶段调用事件处理程序

因此对于DOM0中的代码,可以对一个DOM元素添加多个事件处理程序:

<button id="button">
<script>
    let btn = document.getElementById("button");
    btn.addEventListener("click", ()=>{
        alert('first button');
    },false);
    btn.addEventListener("click", ()=>{
        alert('second button');
    },true);
</script>

输出结果:先输出second button然后再输出first button
可以用removeEventListener()移除事件处理程序,这个时候就无法点击发生对应事件了:

<button id="button">
<script>
    let btn = document.getElementById("button");
    let test =function(){
        alert('test event function remove')
    };
    btn.addEventListener("click", test, false);
    btn.removeEventListener("click", test, false);
</script>

那么为何在addEventListener()添加多个事件处理程序时,不按照程序顺序输出?可以发现修改了输入参数的布尔值,通过不同阶段调用事件处理程序,能够得到不同的事件执行顺序,这就涉及到了事件流。

1.2 事件流

也就是描述页面接收事件的顺序,在早期不同的浏览器厂商对于事件流有不同的实现方案。IE的事件冒泡,Netscape的事件捕获。之后DOM2将事件流进行了统一(所有现代浏览器都支持DOM事件流,只有IE8及更早版本不支持

1.2.1 IE事件流-事件冒泡

事件被定义为从文档树最深节点到外部的节点:比如<div>---<body>---<html>---document
image.png

1.2.2 Netscape事件流-事件捕获

和事件冒泡相反,事件捕获是外部的节点到内部的节点。对于上面的节点,事件访问的顺序应该是:document---<html>---<body>---<div>。事件捕获实际上是为了在事件到达最终目标前拦截事件。
image.png

1.2.3 DOM事件流

1.2.3.1 0级DOM事件模型(非W3C事件模型)

也就是指W3C的DOM标准形成前的事件模型,也就是原始的添加事件监听函数的模型,W3C的DOM标准并不支持的事件模型,具体例子如下:

<!--html部分-->
<input id="myButton" type="button" value="Press Me" οnclick="alert('thanks')">

<!-- js部分 -->
document.getElementById("myButton").onclick = function() {
	alert('thanks');
}
1.2.3.2 2级DOM事件模型

DOM1级于1998年10月1日成为W3C推荐标准,但是1级DOM标准中并没有定义事件相关的内容,所以没有所谓的1级DOM事件模型。2级DOM中除了定义了一些DOM相关的操作之外还定义了一个事件模型 ,这个标准下的事件模型就是我们所说的2级DOM事件模型。
DOM2 事件规范规定了事件流分为三个阶段,会以如下顺序触发事件:

  • 事件捕获
  • 到达目标
  • 事件冒泡

以前面的例子,捕获阶段是从document --> html --> body,div阶段是到达目标阶段,然后事件再进行冒泡,反向传播到document。
image.png

1.2.3.3 3级DOM事件模型

DOM3级没有对事件做任何的修订,所以现代浏览器仍然沿用2级DOM事件标准

计算类型的任务,可以由CPU进行执行,但是对于一些IO操作,文件读写,这个时候CPU就闲置,因此可以执行其他的工作。
JavaScript是单线程的,同一时间只能是执行一个任务,也就是它只能执行计算类的操作,无法操作

1.3 事件对象

当DOM中发生事件时,所有相关信息都会被收集并存储在一个名为event的对象中。这个对象包含了一些基本信息,比如导致事件的元素、发生的事件类型,以及可能与特定事件相关的任何其他数据。这个对象就是事件对象

事件对象只要事件发生时才会 产生,不管是以哪种方式(DOM0或DOM2)指定事件处理程序,都会传入这个event对象。当所有事件处理程序结束后,事件对象就会被销毁。

<button id="button">
<script>
    let btn = document.getElementById("button");
    btn.addEventListener("click", (event)=>{
        alert(event.type);//"click"
    });
</script>

所有事件对象都有这些公共属性:
:::info

  • bubbles 布尔值,表示事件是否是冒泡类型
  • cancelable 布尔值,表示事件是否可以取消默认动作
  • currentTarget 当前目标元素,即添加当前事件处理程序的元素
  • target 实际目标元素,即实际触发事件的元素
  • type 返回当前事件的名称
  • eventPhase 事件传播的当前阶段,1表示捕获阶段
    :::
    公共方法主要有:
    :::info
  1. preventDefault() 通知浏览器不要执行该事件的默认动作,常用于阻止链接的跳转,表单的提交,等标签的默认行为
  2. stopPropagation() 冒泡阶段下,阻止事件的继续向上冒泡
    :::

1.4 事件委托

在JavaScript中,页面事件处理程序的数量与页面整体性能直接相关。所以在使用事件处理程序时,需要对过多的事件处理程序进行优化调整,比如事件委托。事件委托就是利用事件冒泡,使用一个事件处理程序来管理一种类型的事件。
事件委托是通过在所有元素的共同祖先节点添加一个事件处理程序,来达到管理多个事件类型的效果,比如:

<ul>
	<li>列表项1</li>
	<li>列表项2</li>
	<li>列表项3</li>
</ul>
<script>
	var list=document.getElementsByTagName("li");
	for(i=0;i<list.length;i++){
	    list[i].onclick=function(){
	        alert("我是"+e.target);
	    }
	}
</script>

就可以修改为处理祖先节点<li>元素,来统一管理该子元素下的多个事件处理程序

var ul=document.getElementById('ul');
ul.onclick=function(e){
    var e= e || window.event;
    var target = e.target || e.srcElement;
    if(target.nodeName.toLowerCase() === "li"){
        alert("我是"+e.target);
    }
}

2 事件循环(Event Loop)

在具体的代码执行中,JS引擎会常驻内存中,等待着宿主把JS代码或者函数传递给它执行。

在 ES3 和更早的版本中,JavaScript 本身还没有异步执行代码的能力,这也就意味着,宿主环境传递给 JavaScript 引擎一段代码,引擎就把代码直接顺次执行了,这个任务也就是宿主发起的任务。
但是,在 ES5 之后,JavaScript 引入了 Promise,这样,不需要浏览器的安排,JavaScript 引擎本身也可以发起任务了。

在JSC引擎的术语中,把宿主发起的任务称作为宏观任务;把JS引擎发起的任务称为微观任务。在操作系统中,通常等待的行为都是一个事件循环。所以JS没有自己的事件循环系统,它依赖浏览器的事件循环系统。

  • 20
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

归思君

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值