前端培训-中级阶段(2) - 事件(event) 事件冒泡、捕获 - (2019-06-20期)

邓贤
• 阅读 3657

前端最基础的就是 HTML+CSS+Javascript。掌握了这三门技术就算入门,但也仅仅是入门,现在前端开发的定义已经远远不止这些。前端小课堂(HTML/CSS/JS),本着提升技术水平,打牢基础知识的中心思想,我们开课啦(每周四)。

前面我们已经基本掌握常规的语法语义,以及基本的使用方法。接下来我们讲深入进去了解其中内在的原理。

今天我们要讲什么?

  1. 事件机制
  2. 事件对象(Event)
  3. 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 这种定义方式的。

  1. 不可以多次监听事件,因为是赋值的方式,下次赋值会覆盖。
  2. 只可以在冒泡阶段触发

DOM 事件(2 级)

addEventListener 方式定义的。

  1. 可以多次监听,切按监听顺序执行回调(有序)
  2. 取消监听需要同一引用的函数。举个栗子

        // 错误案例,两个方法不是同一引用,导致清除不掉
        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}

事件执行顺序

前端培训-中级阶段(2) - 事件(event) 事件冒泡、捕获 - (2019-06-20期)
图片来源-https://www.w3.org/TR/DOM-Lev...
事件的捕获阶段 > 处于目标阶段 > 事件的冒泡阶段 > 事件的默认行为
这里为什么要强调这个顺序呢?

  1. 因为默认行为是在最后面,所以我们都可以用 e.preventDefault() 来阻止。
  2. 基于上条的阻止默认事件。在移动端滑动时,阻止默认事件需要手动设置 passivefalse
    passive: Boolean,设置为 true 时,表示 listener 永远不会调用 preventDefault()。如果 listener 仍然调用了这个函数,客户端将会忽略它并抛出一个控制台警告。
  3. 我们真正单击的元素事件触发不在冒泡和捕获阶段,而在目标阶段触发DEMO-冒泡&捕获阶段触发事件,可以看到,他是通过定义时的先后顺序来触发的。

事件对象(Event)

Event 对象--mdn

事件对象(属性&方法)

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 时有效
  1. IE: event.cancelBubble=true; //阻止事件冒泡
  2. IE: event.returnValue=false; //阻止事件的默认行为
  3. 获取事件 window.event

事件类型(分类、Event对象之类)

DOM event 子类,根据不同的事件类型,返回的对象会有些许不同,比如 Mouse 类型的,就会有单击坐标之类的。 KeyboardEvent 之类的就会有按键之类的。

前端培训-中级阶段(2) - 事件(event) 事件冒泡、捕获 - (2019-06-20期)

new 一个事件对象

CustomEvent() --mdn

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>setTimeoutsetIntervalsetImmediateI/OUI Rendering
异步任务会在有了结果后,将注册的回调函数放入任务队列中等待主线程空闲的时候(调用栈被清空),被读取到栈内等待主线程的执行。

MicroTask(微任务)

Process.nextTick(Node独有)、PromiseMutationObserver
每个宏任务执行完毕后,会检查 microTask 队列是否有回调,会按照先入先出的规则执行,都执行完再执行宏任务,如此循环。

调用栈

栈采用的是后进先出的规则,这里我们调用 a()a() 内部会调用 aa(), aa() 内部又调用 aa()

function a(){return aa()}
function aa(){return aaa()}
function aaa(){return 1}
  1. a 进栈
  2. aa 进栈
  3. aaa 进栈
  4. aaa 出栈
  5. ...

事件循环的进程模型

  1. 选择任务队列中最先进入的任务,如果任务队列为空,则执行跳转到微任务(MicroTask)的执行步骤
  2. 任务设置为已选择任务
  3. 执行任务
  4. 任务设置为空
  5. 运行完成的任务从任务队列中删除
  6. MicroTasks 步骤:

    1. 进入 MicroTask 检查点
    2. 设置 MicroTask 检查点标志为 true
    3. 当事件循环 MicroTask 不为空时:

      1. 选择最先进入队列的任务,
      2. 设置为已选择的任务
      3. 运行
      4. 将已经执行完成的 MicroTask 改变状态
      5. 移出 MicroTask
    4. 清理IndexDB事务
    5. 设置 MicroTask 检查点的标志为false。
  7. 更新界面渲染。
  8. 返回第一步。

举个栗子(常问无聊题)

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');

结构应该没错

  1. 任务入栈(代码块)
  2. console.log('script start'); 栈中,同步代码,直接输出
  3. function() {console.log('setTimeout');}MacroTask
  4. new Promise 同步代码,执行
  5. 入栈 function(reslove){console.log('Promise-start');reslove();}
  6. 执行 console.log('Promise-start');
  7. 出栈
  8. .then(function() {console.log('Promise-end');})MicroTask
  9. console.log('script end'); 同步代码,输出
  10. 当前执行完出栈,判断 MicroTasks
  11. 执行 console.log('Promise-end');
  12. 完成所有 MicroTasks
  13. 渲染 UI
  14. MacroTasks是否有数据?
  15. 执行 MacroTasks 中第一个。
  16. console.log('setTimeout'); 输出。

前端培训-中级阶段(2) - 事件(event) 事件冒泡、捕获 - (2019-06-20期)

异步事件(消息)

  1. DOM 事件
  2. setTimeout
  3. XHR
  4. Promise

总结

  1. 事件机制

    1. 当前执行块
    2. 当前执行块的微任务队列
    3. 宏任务队列
  2. Event 事件级别
  3. addEventListener 要主要保存 function 的引用,用于解绑
  4. 队列,先进先出(想起了梗,吃多了拉)
  5. 堆栈,先进后出(想起了梗,吃多了吐)
  6. 触发阶段 捕获>目标>冒泡
  7. Event 对象,针对不同的类型,有自己独特的属性。

微信公众号:前端linong

前端培训-中级阶段(2) - 事件(event) 事件冒泡、捕获 - (2019-06-20期)

初级阶段文章目录

  1. 前端培训-初级阶段(17) - 数据存储(cookie、session、stroage)
  2. 前端培训-初级阶段(13) - 正则表达式
  3. 前端培训-初级阶段(13) - 类、模块、继承
  4. 前端培训-初级阶段(13) - ECMAScript (内置对象、函数)
  5. 前端培训-初级阶段(13) - ECMAScript (语法、变量、值、类型、运算符、语句)
  6. 前端培训-初级阶段(13、18)
  7. 前端培训-初级阶段(9 -12)
  8. 前端培训-初级阶段(5 - 8)
  9. 前端培训-初级阶段(1 - 4)

资料

  1. 前端培训目录、前端培训规划、前端培训计划
  2. JavaScript系列----事件机制
  3. 事件参考--mdn
  4. tasks-microtasks-queues-and-schedules
  5. 一次弄懂Event Loop(彻底解决此类面试问题) --光光同学-juejin
点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
美凌格栋栋酱 美凌格栋栋酱
6个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Wesley13 Wesley13
3年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Stella981 Stella981
3年前
Node.js 12中的ES模块[每日前端夜话0x9E]
每日前端夜话0x9E每日前端夜话,陪你聊前端。每天晚上18:00准时推送。正文共:2552字预计阅读时间:10 分钟作者:BrianDeSousa翻译:疯狂的技术宅来源:logrocket!(https://oscimg.oschina.net/oscnet/2ccaf94cecd3
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Wesley13 Wesley13
3年前
Java日期时间API系列36
  十二时辰,古代劳动人民把一昼夜划分成十二个时段,每一个时段叫一个时辰。二十四小时和十二时辰对照表:时辰时间24时制子时深夜11:00凌晨01:0023:0001:00丑时上午01:00上午03:0001:0003:00寅时上午03:00上午0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这