前端最基础的就是 HTML+CSS+Javascript
。掌握了这三门技术就算入门,但也仅仅是入门,现在前端开发的定义已经远远不止这些。前端小课堂(HTML/CSS/JS
),本着提升技术水平,打牢基础知识的中心思想,我们开课啦(每周四)。
前面我们已经基本掌握常规的语法语义,以及基本的使用方法。接下来我们讲深入进去了解其中内在的原理。
今天我们要讲什么?
- 事件机制
- 事件对象(Event)
- event loop
DOM (与事件的关系,看不看无所谓)
DOM(Document Object Model——文档对象模型)是用来呈现以及与任意 HTML 或 XML文档交互的 API。DOM 是载入到浏览器中的文档模型,以节点树的形式来表现文档,每个节点代表文档的构成部分(例如:页面元素、字符串或注释等等)。
DOM 是万维网上使用最为广泛的 API 之一,因为它允许运行在浏览器中的代码访问文件中的节点并与之交互。节点可以被创建,移动或修改。事件监听器可以被添加到节点上并在给定事件发生时触发。
DOM 并不是天生就被规范好了的,它是浏览器开始实现JavaScript时才出现的。这个传统的 DOM 有时会被称为 DOM 0。现在, WHATWG 维护DOM现存标准。
-- MDN
既然 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)
- 可以选择触发阶段(冒泡&捕获)
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
检查点 - 设置
MicroTask
检查点标志为true
-
当事件循环
MicroTask
不为空时:- 选择最先进入队列的任务,
- 设置为已选择的任务
- 运行
- 将已经执行完成的
MicroTask
改变状态 - 移出
MicroTask
。
- 清理IndexDB事务
- 设置
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 对象,针对不同的类型,有自己独特的属性。
微信公众号:前端linong
初级阶段文章目录
- 前端培训-初级阶段(17) - 数据存储(cookie、session、stroage)
- 前端培训-初级阶段(13) - 正则表达式
- 前端培训-初级阶段(13) - 类、模块、继承
- 前端培训-初级阶段(13) - ECMAScript (内置对象、函数)
- 前端培训-初级阶段(13) - ECMAScript (语法、变量、值、类型、运算符、语句)
- 前端培训-初级阶段(13、18)
- 前端培训-初级阶段(9 -12)
- 前端培训-初级阶段(5 - 8)
- 前端培训-初级阶段(1 - 4)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。