详解JS浏览器事件模型

什么是事件

我想你很可能听说过事件驱动, 但是事件驱动到底是什么?为什么说浏览器是事件驱动的呢?

事件驱动通俗地来说就是什么都抽象为事件。

  • 一次点击是一个事件
  • 键盘按下是一个事件
  • 一个网络请求成功是一个事件
  • 页面加载是一个事件
  • 页面报错是一个事件

浏览器依靠事件来驱动APP运行下去,如果没有了事件驱动,那么APP会直接从头到尾运行完,然后结束,事件驱动是浏览器的基石。

一个简单的例子

其实现实中的红绿灯就是一种事件,它告诉我们现在是红灯状态,绿灯状态,还是黄灯状态。 我们需要根据这个事件自己去完成一些操作,比如红灯和黄灯我们需要等待,绿灯我们可以过马路。

下面我们来看一个最简单的浏览器端的事件:

html代码:

<button>Change color</button>

js代码:

var btn = document.querySelector('button');

btn.onclick = function() {
  console.log('button clicked')
}

代码很简单,我们在button上注册了一个事件,这个事件的handler是一个我们定义的匿名函数。当用户点击了这个被注册了事件的button的时候,这个我们定义好的匿名函数就会被执行。

如何绑定事件

我们有三种方法可以绑定事件,分别是行内绑定,直接赋值,用addEventListener。

内联这个方法非常不推荐

html代码:

<button onclick="handleClick()">Press me</button>

然后在script标签内写:

function handleClick() {
  console.log('button clicked')
}

直接赋值

和我上面举的例子一样:

var btn = document.querySelector('button');

btn.onclick = function() {
  console.log('button clicked')
}

这种方法有两个缺点

不能添加多个同类型的handler

btn.onclick = functionA;
btn.onclick = functionB;

这样只有functionB有效,这可以通过addEventListener来解决。

不能控制在哪个阶段来执行,这个会在后面将事件捕获/冒泡的时候讲到。这个同样可以通过addEventListener来解决。

因此addEventListener横空出世,这个也是目前推荐的写法。

addEventListener

旧版本的addEventListener第三个参数是bool,新版版的第三个参数是对象,这样方便之后的扩展,承载更多的功能, 我们来重点介绍一下它。

addEventListener可以给Element,Document,Window,甚至XMLHttpRequest等绑定事件,当指定的事件发生的时候,绑定的回调函数就会被以某种机制进行执行,这种机制我们稍后就会讲到。

语法:

target.addEventListener(type, listener[, options]);
target.addEventListener(type, listener[, useCapture]);
target.addEventListener(type, listener[, useCapture, wantsUntrusted  ]); // Gecko/Mozilla only

type是你想要绑定的事件类型,常见的有click, scroll, touch, mouseover等,旧版本的第三个参数是bool,表示是否是捕获阶段,默认是false,即默认为冒泡阶段。新版本是一个对象,其中有capture(和上面功能一样),passive和once。 once用来执行是否只执行一次,passive如果被指定为true表示永远不会执行preventDefault(),这在实现丝滑柔顺的滚动的效果中很重要。更多请参考Improving scrolling performance with passive listeners

框架中的事件

实际上,我们现在大多数情况都是用框架来写代码,因此上面的情况其实在现实中是非常少见的,我们更多看到的是框架封装好的事件,比如react的合成事件,感兴趣的可以看下这几篇文章。

  • React SyntheticEvent
  • Vue和React的优点分别是什么?两者的最核心差异对比是什么?

虽然我们很少时候会接触到原生的事件,但是了解一下事件对象,事件机制,事件代理等还是很有必要的,因为框架的事件系统至少在这方面还是一致的,这些内容我们接下来就会讲到。

事件对象

所有的事件处理函数在被浏览器执行的时候都会带上一个事件对象,举个例子:

function handleClick(e) {
  console.log(e);
}  

btn.addEventListener('click', handleClick);

这个e就是事件对象,即event object。 这个对象有一些很有用的属性和方法,下面举几个常用的属性和方法。

属性

  • target
  • x, y等位置信息
  • timeStamp
  • eventPhase

方法

  • preventDefault 用于阻止浏览器的默认行为,比如a标签会默认进行跳转,form会默认校验并发送请求到action指定的地址等
  • stopPropagation 用于阻止事件的继续冒泡行为,后面讲事件传播的时候会提到。

事件传播

前面讲到了事件默认是绑定到冒泡阶段的,如果你显式令useCapture为true,则会绑定到捕获阶段。

事件捕获很有意思,以至于我会经常出事件的题目加上一点事件传播的机制,让候选人进行回答,这很能体现一个人的水平。了解事件的传播机制,对于一些特定问题有着非常大的作用。

一个Element上绑定的事件触发了,那么其实会经过三个阶段。

第一个阶段 - 捕获阶段

从最外层即HTML标签开始,检查当前元素有没有绑定对应捕获阶段事件,如果有则执行,没有则继续往里面传播,这个过程递归执行直到触达触发这个事件的元素为止。

伪代码:

function capture(e, currentElement) {
    if (currentElement.listners[e.type] !== void 0) {
        currentElement.listners[e.type].forEach(fn => fn(e))
    }

    // pass down
    if (currentElement !== e.target) {
        // getActiveChild用于获取当前事件传播链路上的子节点
        capture(e, getActiveChild(currentElement, e))
    } else {
        bubble(e, currentElement)
    }
}

// 这个Event对象由引擎创建
capture(new Event(), document.querySelector('html'))

第二个阶段 - 目标阶段

上面已经提到了,这里省略了。

第三个阶段 - 冒泡阶段

从触发这个事件的元素开始,检查当前元素有没有绑定对应冒泡阶段事件,如果有则执行,没有则继续往里面传播,这个过程递归执行直到触达HTML为止。

伪代码:

function bubble(e, currentElement) {
    if (currentElement.listners[e.type] !== void 0) {
        currentElement.listners[e.type].forEach(fn => fn(e))
    }
    // returning
    if (currentElement !== document.querySelector('html')) {
        bubble(e, currentElement.parent)
    }
}

上述的过程用图来表示为:

如果你不希望事件继续冒泡,可以用之前我提到的stopPropagation。

伪代码:

function bubble(e, currentElement) {
    let stopped = false;
    function cb() {
        stopped = true;
    }
    if (currentElement.listners[e.type] !== void 0) {
        currentElement.listners[e.type].forEach(fn => {
            fn({
                ...e,
                stopPropagation: cb
            });
            if (stopped) return;
        })
    }
    // returning
    if (currentElement !== document.querySelector('html')) {
        bubble(e, currentElement.parent)
    }
}

事件代理

利用上面提到的事件冒泡机制,我们可以选择做一些有趣的东西。 举个例子:

我们有一个如下的列表,我们想在点击对应列表项的时候,输出是点击了哪个元素。

HTML代码:

<ul>
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
</ul>

JS代码:

document.querySelector('ul').addEventListener('click', e => console.log(e.target.innerHTML))

在线地址,上面说了addEventListener会默认绑定到冒泡阶段,因此事件会从目标阶段开始,向外层冒泡,到我们绑定了事件的ul上,ul中通过事件对象的target属性就能获取到是哪一个元素触发的。

“事件会从目标阶段开始”,并不是说事件没有捕获阶段,而是我们没有绑定捕获阶段,我描述给省略了。

我们只给外层的ul绑定了事件处理函数,但是可以看到li点击的时候,实际上会打印出对应li的内容(1,2,3或者4)。 我们无须给每一个li绑定事件处理函数,不仅从代码量还是性能上都有一定程度的提升。

这个有趣的东西,我们给了它一个好听的名字“事件代理”。在实际业务中我们会经常使用到这个技巧,这同时也是面试的高频考点。

总结

事件其实不是浏览器特有的,和JS语言也没有什么关系,这也是我为什么没有将其划分到JS部分的原因。很多地方都有事件系统,但是各种事件模型又不太一致。

我们今天讲的是浏览器的事件模型,浏览器基于事件驱动,将很多东西都抽象为事件,比如用户交互,网络请求,页面加载,报错等,可以说事件是浏览器正常运行的基石。

我们在使用的框架都对事件进行了不同程度的封装和处理,除了了解原生的事件和原理,有时候了解一下框架本身对事件的处理也是很有必要的。

当发生一个事件的时候,浏览器会初始化一个事件对象,然后将这个事件对象按照一定的逻辑进行传播,这个逻辑就是事件传播机制。 我们提到了事件传播其实分为三个阶段,按照时间先后顺序分为捕获阶段,目标阶段和冒泡阶段。开发者可以选择监听不同的阶段,从而达到自己想要的效果。

事件对象有很多属性和方法,允许你在事件处理函数中进行读取和操作,比如读取点击的坐标信息,阻止冒泡等。

最后我们通过一个例子,说明了如何利用冒泡机制来实现事件代理。

以上就是详解JS浏览器事件模型的详细内容,更多关于JS浏览器事件模型的资料请关注我们其它相关文章!

(0)

相关推荐

  • 关于javascript DOM事件模型的两件事

    事件捕捉(Event Capture)的实现问题 W3C DOM Level2的事件模型规范中,事件在DOM树中的传播过程(从根节点到目标节点)被分为了两个阶段:捕捉(Capture)和冒泡(Bubbling).下面这个图能大概的说明整个过程: (from W3C) 如果想创建一个捕捉事件,在支持W3C 事件模型的浏览器中,将addEventListener的第三个参数设为true就好了.例如: 复制代码 代码如下: document.getElementById('foo').addEvent

  • javascript事件捕获机制【深入分析IE和DOM中的事件模型】

    本文实例分析了javascript事件捕获机制.分享给大家供大家参考,具体如下: 1.什么是事件冒泡? 在排序算法中,我们学过冒泡排序法,所谓冒泡就是让底层的东西浮出水面,对于事件冒泡也同样是如此, 下面我们来看一个例子来说明什么是事件冒泡. <div> <button>测试</button> </div> <script> $("div").bind("click",function(){alert(&q

  • javascript事件模型介绍

    一.原始事件模型 其事件类型:分为"输入事件(如onclicki)"和"语义事件(如onsubmit)" 事件程序的注册可以以下几种方式: 1.JS代码作为HTML性质值 复制代码 代码如下: <input type="button" value="Press me" onclick="alert('thanks');" 2.事件处理程序作为JS属性 附注:文档中的每个HTML元素在文档树中都有一个相

  • javascript事件模型实例分析

    本文实例讲述了javascript事件模型的用法.分享给大家供大家参考.具体分析如下: 一.事件模型 冒泡型事件(Bubbling):事件由叶子节点沿祖先节点一直向上传递到根节点 捕获型事件(Capturing):由DOM树最顶元素一直到最精确的元素,与冒泡型事件相反 DOM标准事件模型:DOM标准既支持冒泡型事件,也支持捕获型事件,可以说是两者的结合体,首先是捕获型,接着冒泡传递 二.事件对象 在IE浏览器中事件对象是window的一个属性,在DOM标准中,event必须作为唯一的参数传给事件

  • js事件模型与自定义事件实例解析

    JavaScript 一个最简单的事件模型,需要有事件绑定与触发,还有事件删除. var eventModel = { list: {}, bind: function () { var args = [].slice.call(arguments), type = args[0], handlers = args.slice(1); if (typeof type === 'string' && handlers.length > 0) { for (var i = 0; i &l

  • javascript 传统事件模型构造的事件监听器实现代码

    代码如下: 复制代码 代码如下: var br={}; br.eventRouter = {}; br.eventRouter.addListen = function(el,eventType,func){ if(!el.eventObjs){ el.eventObjs = {}; } if(!el.eventObjs[eventType]){ el.eventObjs[eventType]=[]; br.eventRouter.bindListen(el,eventType); } el.e

  • 带你快速理解javascript中的事件模型

    javascript中有两种事件模型:DOM0,DOM2.而对于这两种的时间模型,我一直不是非常的清楚,现在通过网上查阅资料终于明白了一些. 一.  DOM0级事件模型 DOM0级事件模型是早期的事件模型,所有的浏览器都是支持的,而且其实现也是比较简单.代码如下: <p id = 'click'>click me</p> <script> document.getElementById('click').onclick = function(event){ alert(

  • Javascript基础知识(一)核心基础语法与事件模型

    一.Javascript核心基础语法 1.Javascript是一门增加交互效果的编程语言,它最初由网景公司发明,最后提交给ECMA(欧洲计算机制造商协会),ECMA将Javascript标准化,其命名为Javascript. 2.Javascript是一门解释性语言,无需编译就可以直接在浏览器下运行. 3.Javascript的用途? 1.可以控制网页中所有元素,增加.删除.修改元素的属性. 2.可以在html中放入动态文本. 3.响应用户在使用网页时产生的事件. 4.校验用户输入的数据. 5

  • javascript事件模型代码

    本节稍稍深入地讨论关于事件处理的话题,如果你对模式.闭包和面向对象等概念还不太理解,不妨暂且等阅读完相关内容之后再回过头来阅读它,相信你会有很大收获. 1 事件处理模式 在程序设计领域,"事件处理"是一种模式,当一个对象受外部影响而改变状态时,通过消息的方式将这个状态改变通知给这个对象或者相关联的某个对象,让它执行对应的动作,这就是事件处理的基本原理.负责通知状态改变的对象被称作"消息",而执行响应动作的属性则被称作"事件代理".        

  • 详解JS浏览器事件模型

    什么是事件 我想你很可能听说过事件驱动, 但是事件驱动到底是什么?为什么说浏览器是事件驱动的呢? 事件驱动通俗地来说就是什么都抽象为事件. 一次点击是一个事件 键盘按下是一个事件 一个网络请求成功是一个事件 页面加载是一个事件 页面报错是一个事件 浏览器依靠事件来驱动APP运行下去,如果没有了事件驱动,那么APP会直接从头到尾运行完,然后结束,事件驱动是浏览器的基石. 一个简单的例子 其实现实中的红绿灯就是一种事件,它告诉我们现在是红灯状态,绿灯状态,还是黄灯状态. 我们需要根据这个事件自己去完

  • 详解JS浏览器事件循环机制

    先来明白些概念性内容. 进程.线程 进程是系统分配的独立资源,是 CPU 资源分配的基本单位,进程是由一个或者多个线程组成的. 线程是进程的执行流,是CPU调度和分派的基本单位,同个进程之中的多个线程之间是共享该进程的资源的. 浏览器内核 浏览器是多进程的,浏览器每一个 tab 标签都代表一个独立的进程(也不一定,因为多个空白 tab 标签会合并成一个进程),浏览器内核(浏览器渲染进程)属于浏览器多进程中的一种. 浏览器内核有多种线程在工作. GUI 渲染线程: 负责渲染页面,解析 HTML,C

  • 详解JS浏览器储存

    引言 随着对前端的了解越来越深入,了解到了很多种浏览器的存储方案,如 Cookie.LocalStorage等,哪这些存储方案有何异同,分别的适用场景又是什么呢. Cookie Cookie的来源 Cookie 被创造出来的本意并不是本地储存,而是为了辨别用户身份.众所周知,Http 协议是无状态的,也就是说你每一次发送给服务器的请求对于服务器来说都是孤立的,服务器不知道这这些请求来自于谁.比如你向购物车里面添加了一些商品,但是当发送结账请求的时候服务器懵了,我怎么知道你是谁,你买了什么呢.而使

  • 详解js的事件代理(委托)

    JavaScript事件代理(委托)一般用于以下情况: 1. 事件注册在祖先级元素上,代理其子级元素.可以减少事件注册数量,节约内存开销,提高性能. 2. 对js动态添加的子元素可自动绑定事件. 之前一直用各种js库的事件代理,如 jQuery,非常方便实用.今天尝试用原生 js 实现该功能. var addEvent = (function () { if (document.addEventListener) { return function (element, type, handler

  • 一文详解JS中的事件循环机制

    目录 前言 1.JavaScript是单线程的 2.同步和异步 3.事件循环 前言 我们知道JavaScript 是单线程的编程语言,只能同一时间内做一件事,按顺序来处理事件,但是在遇到异步事件的时候,js线程并没有阻塞,还会继续执行,这又是为什么呢?本文来总结一下js 的事件循环机制. 1.JavaScript是单线程的 JavaScript 是一种单线程的编程语言,只有一个调用栈,决定了它在同一时间只能做一件事.在代码执行的时候,通过将不同函数的执行上下文压入执行栈中来保证代码的有序执行.在

  • 详解js几个绕不开的事件兼容写法

    本文介绍了详解js几个绕不开的事件兼容写法,分享给大家,具体如下: 1.键盘事件 keyCode 兼容性写法 var inp = document.getElementById('inp') var result = document.getElementById('result') function getKeyCode(e) { e = e ? e : (window.event ? window.event : "") return e.keyCode ? e.keyCode :

  • 详解JS事件循环及宏任务微任务的原理

    目录 宏任务 微任务 事件循环 宏任务与微任务 微任务中创建宏任务 宏任务中创建微任务 宏任务中创建宏任务 微任务中创建微任务 总结 本质上来说,JavaScript是同步的.阻塞的.单线程语言,不管是在浏览器中还是nodejs环境下.浏览器在执行js代码和渲染DOM节点都是在同一个线程中,执行js代码就无法渲染DOM,渲染DOM的时候就无法执行js代码.如果按照这种同步方式执行,页面的渲染将会出现白屏甚至是报错,特别是遇到一些耗时比较长的网络请求或者js代码,因此在实际开发中一般是通过异步的方

  • 实例详解JS中的事件循环机制

    目录 一.前言 二.宏.微任务 三.Tick 执行顺序 四.案例详解 1.掺杂setTimeout 2.掺杂微任务,此处主要是Promise.then 3.掺杂async/await 一.前言 之前我们把react相关钩子函数大致介绍了一遍,这一系列完结之后我莫名感到空虚,不知道接下来应该更新有关哪方面的文章.最近想了想,打算先回归一遍JS基础,把一些比较重要的基础知识点回顾一下,然后继续撸框架(可能是源码.也可能补全下全家桶).不积跬步无以至千里,万丈高楼咱们先从JS的事件循环机制开始吧,废话

  • 详解js中构造流程图的核心技术JsPlumb(2)

    前言:上篇详解js中构造流程图的核心技术JsPlumb介绍了下JsPlumb在浏览器里面画流程图的效果展示,以及简单的JsPlumb代码示例.这篇还是接着来看看各个效果的代码说明. 一.设置连线的样式和颜色效果代码示例 大概的效果如图: 这些效果看着很简单,那么,我们如何用代码去实现它呢.上章我们说过,JsPlumb的连线样式是由点的某些属性决定的,既然如此,我们就通过设置点的样式来动态改变连线的样式即可.来看代码: 首先来看看连线类型的那个select <div id="btn_line

  • 详解JS异步加载的三种方式

    一:同步加载 我们平时使用的最多的一种方式. <script src="http://yourdomain.com/script.js"></script> <script src="http://yourdomain.com/script.js"></script> 同步模式,又称阻塞模式,会阻止浏览器的后续处理,停止后续的解析,只有当当前加载完成,才能进行下一步操作.所以默认同步执行才是安全的.但这样如果js中有输

随机推荐