今天我们要讲什么?
- 事件机制
- 事件对象(Event)
- event loop
DOM (与事件的关系,看不看无所谓)
DOM(Document Object Model——文档对象模型)是用来呈现以及与任意 HTML 或 XML文档交互的 API。DOM 是载入到浏览器中的文档模型,以节点树的形式来表现文档,每个节点代表文档的构成部分(例如:页面元素、字符串或注释等等)。
DOM 是万维网上使用最为广泛的 API 之一,因为它允许运行在浏览器中的代码访问文件中的节点并与之交互。节点可以被创建,移动或修改。事件监听器可以被添加到节点上并在给定事件发生时触发。
DOM 并不是天生就被规范好了的,它是浏览器开始实现JavaScript时才出现的。这个传统的 DOM 有时会被称为 DOM 0。现在, WHATWG 维护DOM现存标准。
既然 DOM
有版本,那么在他的环境上事件的支持也是有版本的。
DOM 事件(0 级)
body.onclick
这种定义方式的。
- 不可以多次监听事件,因为是赋值的方式,下次赋值会覆盖。
- 只可以在冒泡阶段触发
DOM 事件(2 级)
addEventListener
方式定义的。
- 可以多次监听,切按监听顺序执行回调(有序)
- 取消监听需要同一引用的函数。举个栗子
// 错误案例,两个方法不是同一引用,导致清除不掉 document.addEventListener('click', function(){}) document.removeEventListener('click', function(){}) // 正确案例,同一引用,可以清除。 function documentClick(){} document.addEventListener('click', documentClick) document.removeEventListener('click', documentClick)
3.可以选择触发阶段(冒泡&捕获) capture
事件机制
标准事件:EMCAScript 标准规定事件流包含三个阶段,分别为事件捕获阶段,目标阶段,事件冒泡阶段。
先存个代码,之后的例子我们用这个例子。测试看我这里的 DEMO
<html onclick="alert('html')"> <body onclick="alert('body')"> <a onclick="alert('a')">click</a> </body> </html>
事件捕获阶段
捕获阶段:由外到内,触发规律为 html > body > a
。
如果想在捕获阶段就触发,需要传入参数 {capture: true}
事件冒泡阶段
冒泡阶段:由内到外,触发规律为 a > body > html
这个阶段执行是 W3C
默认的,等价于 {capture: false}
事件执行顺序
图片来源-https://www.w3.org/TR/DOM-Lev...
事件的捕获阶段 > 处于目标阶段 > 事件的冒泡阶段 > 事件的默认行为
这里为什么要强调这个顺序呢?
- 因为默认行为是在最后面,所以我们都可以用
e.preventDefault()
来阻止。
- 基于上条的阻止默认事件。在移动端滑动时,阻止默认事件需要手动设置
passive
为false
。
passive: Boolean
,设置为 true
时,表示 listener
永远不会调用 preventDefault()
。如果 listener
仍然调用了这个函数,客户端将会忽略它并抛出一个控制台警告。
- 我们真正单击的元素的事件触发不在冒泡和捕获阶段,而在目标阶段触发。 DEMO-冒泡&捕获阶段触发事件,可以看到,他是通过定义时的先后顺序来触发的。
事件对象(Event)
事件对象(属性&方法)
key | 类型 | 描述 |
bubbles | boolean | 是否冒泡 |
cancelable | boolean | 是否可以取消的默认动作。 |
currentTarget | Element | 返回其事件监听器触发该事件的元素。(this 的真实指向) |
eventPhase | Intenger | 返回事件传播的当前阶段 |
target | Element | 返回触发此事件的元素。(事件的目标节点) |
timeStamp | Date | 触发的时间戳 |
type | String | 事件名称。 |
isTrusted | boolean | 该事件是否是浏览器生成(true 代表是,false 代表是开发人员创建 |
preventDefault | Function | 取消事件的默认行为在 cancelable=true 时有效 |
stopPropagation | Function | 取消事件的捕获或者冒泡行为在 bubbles=true 时有效 |
- IE:
event.cancelBubble=true;
//阻止事件冒泡
- IE:
event.returnValue=false;
//阻止事件的默认行为
- 获取事件
window.event
事件类型(分类、Event对象之类)
DOM event 子类,根据不同的事件类型,返回的对象会有些许不同,比如 Mouse
类型的,就会有单击坐标之类的。 KeyboardEvent
之类的就会有按键之类的。
new 一个事件对象
document.body.onclick=function(e){console.log(e)} var btn=document.body; var event= new CustomEvent("click"); btn.dispatchEvent(event);
其实这里我们可以自定义事件的名称,然后我们就可以实现一个发布订阅的功能。
document.addEventListener("bus", function(e) { console.log(e, e.detail) }); var event = new CustomEvent("bus", {detail: {LN_type: 'lilnong.top'}}); document.dispatchEvent(event);
event loop (事件循环)
首先,我们要牢记一件事情 js 是单线程
Event Loop
中文叫事件循环。是浏览器内部的一种机制,javaScript 单线程运行时如何不阻塞 UI
。
Javascript
有一个 main thread 主线程
和 call-stack 调用栈(执行栈)
,所有的任务都会被放到调用栈(栈采用的是后进先出的规则)等待主线程执行。
任务类别&任务队列(Task Queue)
在 JavaScript
中,任务被分为两种,一种宏任务(MacroTask)也叫Task,一种叫微任务(MicroTask)。
MacroTask(宏任务)
<script>
、setTimeout
、setInterval
、setImmediate
、I/O
、UI Rendering
。
异步任务会在有了结果后,将注册的回调函数放入任务队列中等待主线程空闲的时候(调用栈被清空),被读取到栈内等待主线程的执行。
MicroTask(微任务)
Process.nextTick
(Node独有)、Promise
、MutationObserver
每个宏任务执行完毕后,会检查 microTask
队列是否有回调,会按照先入先出的规则执行,都执行完再执行宏任务,如此循环。
调用栈
栈采用的是后进先出的规则,这里我们调用 a()
,a()
内部会调用 aa()
, aa()
内部又调用 aa()
。
function a(){return aa()} function aa(){return aaa()} function aaa(){return 1}
- a 进栈
- aa 进栈
- aaa 进栈
- aaa 出栈
- ...
事件循环的进程模型
- 选择任务队列中最先进入的任务,如果任务队列为空,则执行跳转到微任务(MicroTask)的执行步骤
- 任务设置为已选择任务
- 执行任务
- 任务设置为空
- 运行完成的任务从任务队列中删除
MicroTasks
步骤:
- 进入
MicroTask
检查点
b.设置 MicroTask
检查点标志为 true
c.当事件循环MicroTask
不为空时:
- 选择最先进入队列的任务,
- 设置为已选择的任务
- 运行
- 将已经执行完成的
MicroTask
改变状态 - 移出
MicroTask
。
d.清理IndexDB事务
e.设置 MicroTask
检查点的标志为false。
- 更新界面渲染。
- 返回第一步。
举个栗子(常问无聊题)
console.log('script start'); setTimeout(function() { console.log('setTimeout'); }, 0); new Promise(function(reslove){ console.log('Promise-start') reslove(); }).then(function() { console.log('Promise-end'); }) console.log('script end');
结构应该没错
- 任务入栈(代码块)
console.log('script start');
栈中,同步代码,直接输出
function() {console.log('setTimeout');}
入MacroTask
new Promise
同步代码,执行
- 入栈
function(reslove){console.log('Promise-start');reslove();}
- 执行
console.log('Promise-start');
- 出栈
.then(function() {console.log('Promise-end');})
进MicroTask
console.log('script end');
同步代码,输出
- 当前执行完出栈,判断
MicroTasks
- 执行
console.log('Promise-end');
- 完成所有
MicroTasks
- 渲染 UI
MacroTasks
是否有数据?
- 执行
MacroTasks
中第一个。
console.log('setTimeout');
输出。
异步事件(消息)
DOM
事件setTimeout
XHR
Promise
总结
- 事件机制
- 当前执行块
- 当前执行块的微任务队列
- 宏任务队列
- Event 事件级别
- addEventListener 要主要保存 function 的引用,用于解绑
- 队列,先进先出(想起了梗,吃多了拉)
- 堆栈,先进后出(想起了梗,吃多了吐)
- 触发阶段 捕获>目标>冒泡
- Event 对象,针对不同的类型,有自己独特的属性。