DOM4标准的事件监听与滚屏优化

news2025/3/13 16:56:57

EventTarget.addEventListener()

我们在学习addEventListener()时都只是知道它是用来给事件注册事件处理函数的。但是这种描述并不是很准确,MDN上给我们准确的描述了它的定义。EventTarget.addEventListener()方法将指定的监听器注册到EventTarget上,当该对象触发指定的事件时,指定的回调函数就会被执行。EventTarget目标对象可以是一个文档上的元素ELement、Document、Window或者是任何其它支持事件的对象,例如XMLHTTPRequest
addEventListener()的工作原理是将实现EventListener的函数或者对象添加到调用它的EventTarget上的指定事件类型的事件侦听器列表中。

EventTarget

EventTarget是一个DOM接口,由可以接收事件、并且可以创建侦听器的对象实现。
Element、Document、Window是最常见的EventTargets,但是其它的对象也可以作为EventTargets,比如XMLHTTPRequest、AudioNode、AudioContext等等。
许多EventTargets包括(elements、documents、windows)支持通过onEvent特性和属性设置事件处理函数event handlers
首先EventTarget()是一个构造函数,通过实例化new EventTarget()构造函数创建一个新的EventTarget实例对象,在EventTarget.prototype上存在三个方法:addEventListener()dispatchEvent()removeEventListener()addEventListener()方法在EventTraget上注册特定的事件类型的事件处理函数。removeEventListener()方法删除EventTarget事件处理函数。dispatchEvent()将事件分派到EventTarget
image.png
那么EventTarget事件目标构造函数存在的意义是什么呢?我们看下面的例子中,通过EventTarget构造函数实例化的对象可以继承EventTarget.prototype方法,那么就说明此时的obj对象是一个EventTarget对象。而一个普通对象,因为不是EventTarget对象,所以不能够继承到EventTarget.prototype的方法。

  1. EventTarget构造函数的第一个作用就是创建EventTarget对象。
  2. 让其它的EventTargets对象通过原型链的方式继承到EventTarget.prototype上的方法,例如window、document、Element
const obj = new EventTarget();
obj.addEventListener();

const obj = {};
obj.addEventListener(); // Uncaught TypeError: obj.addEventListener is not a function.

为什么window对象可以通过window.addEventListener()的形式注册事件处理函数?window对象种并不存在addEventListener()方法,而addEventListener()方法存在EventTarget.prototype上,但是由于原型链的作用。window对象通过原型链的方式从EventTarget.prototype上继承addEventListener()方法,这也正是window对象为什么能够调用addEventListener()方法的原因。

console.log(window);

image.png

EventTarget的工作方式及简单实现

EventTarget的工作方式主要是利用EventTarget.prototype上的方法。
addEventListener()方法:在EventTarget上注册特定事件类型的事件处理程序。
removeEventListener()方法:在EventTarget中删除事件侦听器事件处理函数。
dispatchEvent()方法:将事件派发到EventTarget上。
熟悉方法之后,我们就要将这几个方法进行重写。
封装EventTarget构造函数的思路是什么呢?首先我们需要分析事件到底是处于什么样的时机触发的呢?我们如何去执行事件触发之后的事件处理函数?其实也是很简单的,我们没有办法具体的控制事件处理函数执行的时机,因为事件处理函数是在事件触发的时候执行的,但是我们不知道事件具体是在什么时候触发的。那么如何处理呢?我们可以通过数组的方式,将该事件类型绑定的事件处理函数都存放到数组内部保存,当事件派发dispatch的时候,将数组中存放的事件处理函数都拿出来执行。

/**
*	EventTarget构造函数
* listeners:存放事件类型,事件回调函数的对象
*/
var EventTarget = function() {
	this.listeners = {};
}

/**
* 为什么在prototype重写声明一遍listeners,因为让其它EventTargets对象继承,
* 例如:window、document
*/
EventTarget.prototype.listeners = null;


/**
* @description: addEventListener
* type事件类型不存在listener中,我们就创建一个数组,用来存放该事件回调函数;
* type事件类型存在listener中,将事件回调函数放入对应的事件类型数组中;
* @param {*} type: 事件类型
* @param {*} callback: 事件回调函数
* @return {*} undefined
*/
EventTarget.prototype.addEventListener = function(type, callback) {
	// 事件类型是否存在listeners中
	if (!(type in this.listeners)) {
		this.listeners[type] = [];
	}
	this.listeners[type].push(callback);
}


/**
* @description: removeEventListener
* 移除绑定的事件处理函数,注意 stack[i] === callback,
* 这就是为什么你需要移除事件监听函数时,必须在addEventListener绑
*	定事件处理函数是具名函数的原因,因为匿名函数无法判断是否相等。
* @param {*} type: 事件类型
* @param {*} callback: 事件回调函数
* @return undefined
*/
EventTarget.prototype.removeEventListener = function(type, callback) {
	// 事件类型是否存在listeners中
	if (!(type in this.listeners)) {
		return;
	}
	// stack表示该事件回调数组[]
	var stack = this.listeners[type],
			len = stack.length;
	for (var i = 0; i < len; i++) {
		if (stack[i] === callback) {
			this.listeners.splice(i, 1);
		}	
	}
}


/**
* @description: dispatchEvent
* 向一个指定的事件目标派发一个事件,
* 并以合适的顺序同步调用目标元素相关的
*	事件处理函数。
* @param {*} event:要派发的事件对象
* @param {*} target:用来初始化事件和决定将会触发目标
*/
EventTarget.prototype.dispatchEvent = function(event) {
	// 事件类型是否存在listeners中
	if (!(event.type in this.listeners)) {
		return;
	}
	var stack = this.listeners[event.type],
			len = stack.length;
	event.target = this;
	for (var i = 0; i < len; i++) {
		stack[i].call(this, event);
	}
}

EventTarget.dispatchEvent()深入到Event构造函数

EventTarget.dispatchEvent()方法与浏览器原生事件有什么不同?浏览器原生事件,是由DOM派发的,并通过Event loop异步调用事件处理程序,而dispatchEvent()则是同步调用事件处理程序。在调用dispatchEvent()后,所有监听该事件的事件处理程序将在代码前执行返回。
dispatchEvent()方法是create-init-dispatch过程中的最后一步,用于将事件调用到实现的事件模型中。可以利用Event构造函数创建事件。这是MDN文档上对于dispatchEvent方法的介绍,既然介绍到了Event构造函数,我们就一起来看看如何自定义事件对象Event
注意一哈,事件是否能够取消,事件处理函数中是否阻止过事件默认行为,这都是可以获取到的。e.cancelable作为Event实例的只读属性,表明事件是否可以被取消。e.defaultPrevented判断处理函数中是否阻止过事件默认行为,换句话说就是在事件处理函数中是否调用过e.preventDefault()方法。

Event()构造函数,创建一个新的事件对象Event。

event = new Event(typeArg, eventInit);

typeArg: 
	表示所创建事件的名称。

eventInit:
	是EventInit类型的字典,接受以下的字段:
		·"bubbles",可选,Boolean类型,默认值为false,表示事件是否冒泡。
		·"cancelable",可选,Boolean类型,默认值为false,表示该事件是否能被取消。
		·"composed",可选,Boolean类型,默认值为false,指示事件是否会在影子DOM根节点之外触发侦听器。

熟悉Event构造函数,我们来尝试自定义一个事件,然后利用dispatchEvent()方法将事件派发到EventTarget对象上。下面例子中,我自定义了一个事件see,并且这个see事件支持冒泡,不支持取消,通过dispatchEvent方法将事件派发到EventTarget对象(oDiv)上,此时oDiv元素就能够监听到我自定义的see事件。
注意下面例子中,虽然事件see的事件处理程序中调用了e.preventDefault()方法,但是e.defaultPrevented依旧返回false,这是为什么?因为see事件在定义的时候,我们将cancelable字段设置为false,也就是表明事件see不可取消,事件处理程序中无法监听回调中停止事件。所以,e.defaultPrevented字段的结果返回false

var ev = new Event('see', {
	bubbles: true,
	cancelable: false
});

var oDiv = document.getElementsByTagName('div')[0];

oDiv.addEventListener('see', function(e) {
	e.preventDefault();
	console.log(e.defaultPrevented); // false
	console.log('Listening event see....');
	console.log(e.cancelable); // false 事件不可取消
});

oDiv.dispatchEvent(ev);

EventTarget.addEventListener()深入到滚屏优化

MDN文档上指出EventTarget.addEventListener()方法将指定的监听器注册到EventTarget上,当该对象触发指定的事件时,指定的回调函数就会被执行。事件目标可以是一个文档上的元素Element、Document、Window或者任何支持事件的对象,比如XMLHTTPRequest
addEventListener()工作原理是将实现EventListener的函数或对象添加到调用它的EventTarget上指定事件类型的事件侦听器列表中,与我们上面重写addEventListener()方法的逻辑一致。
上面简述是MDN文档对addEventListener()方法的定义,我们之前学习addEventListener()方法时并没有仔细的看addEventListener()方法的参数,Vue中的事件修饰符与addEventListener()方法中的options参数特别相似。
addEventListener(eventType, handler, useCapture || options);这是addEventListener()方法标准的语法,其中eventType表示监听的事件类型,hanlder表示事件处理函数,useCapture表示事件流中两种事件传播方式,false表示选择事件冒泡的方式触发事件处理函数,true表示选择事件捕获的方式触发事件处理函数。

oDiv.addEventListener('click',function(){},false);

上面的例子中,是我们最常用的方式。但是在DOM4的标准里,addEventListener()方法中的还可以设置options参数,options参数表示:一个指定有关listener(事件处理程序) 属性的可选参数对象。可选的参数默认都是false,可选参数有:
captureBoolean,表示listener会在该类型的事件捕获阶段阶段传播到该EventTarget时触发,与我们上面分析的useCapture是同一个意思。
onceBoolean,表示listener在添加之后最多调用一次。如果是true,listener会在其被调用之后自动移除。
passiveBoolean,设置为true时,表示listener永远不会调用ev.preventDefault()方法,如果你仍然在listener调用了ev.preventDefault()方法,浏览器会在控制台中抛出警告unable to prevetDefault inside passive event listener invocation;(无法在被动事件侦听器调用内预先设置默认值)。

滚屏优化

在学习addEventListener()方法中的passive字段时,MDN文档中上提出了一个“使用passive改善滚屏性能”的概念。
:::info
下面例子中是在Chrome浏览器下中测试的结果。
:::
在了解“使用passive改善的滚屏性能”概念之前,我们先看下面的例子。我们在window对象上增加touchstart事件的事件处理函数function(e){}。只要我们触摸到屏幕开始滚动的时候,就会执行调用绑定的事件处理函数function(e){}

window.addEventListener('touchstart',function(e){
	console.log('Listening scroll....');
});

那么我现在想阻止touchstart事件的默认行为,touchstart事件的默认行为是什么呢?touchstart事件的默认行为其实就是srcoll滚动。因为你触摸点击屏幕开始滚动的时候,touchstart事件的listener侦听器就开始执行。那我现在尝试阻止touchstart事件的默认行为,例如下面的例子。
我在事件处理函数listeners中增加了e.preventDefault()语句,希望阻止touchstart事件的默认行为,也就是说我不想让屏幕滚动了,并且我还在listeners中增加e.defaultPrevented语句判断是否调用过e.preventDefault()方法。但是结果如下面的截图一样,这样的方式不仅仅没有成功的阻止touchstart事件的默认行为,而且浏览器还抛出了异常。这是为什么?
我们通过对异常的分析,错误提示我们:passive无法阻止去调用默认行为,事件处理函数listener取决于目标开始时的passive状态。换句话来说,就是passive字段现在的状态相当于设置成true,因为上面我们说过,passivetrue的时候,listener永远不会调用ev.preventDefault(),如果你强制调用,则会抛出异常。

window.addEventListener('touchstart',function(e){
	e.preventDefault();
	console.log('Listening scroll....');
		console.log(e.defaultPrevented); // false
});

image.png
为什么会出现上述的这种情况呢?
根据MDN文档,passive选项的默认值始终为false。但是某些浏览器(特别是Chrome和Firefox)已将文档级节点Window、Document、Document.bodytouchstarttouchmove事件的passive选项默认值设置为true。所以上面两种情况在listeners中调用ev.preventDefault()方法并不能够成功阻止事件的默认行为。但是浏览器为什么要这样做?
因为在监听touchstart事件的时候,用户如果触发了touchstart事件,那么touchstart事件绑定的处理函数listeners在执行的时候,内部如果有阻止默认行为的代码时,就不会再去执行默认行为了。如果内部没有阻止默认行为的代码时,下一步就会执行默认行为。但是无论是否阻止默认行为,它都会有一个等待的时间,因为必须等待listener执行程序完成后,再去执行默认行为(滚动),此时等待的时间会造成滚动的卡顿。所以执行的顺序是:listeners —> 执行默认行为,所以这种执行顺序在主线程中内部存在非常大的性能问题,由于有等待的时间,导致滚动卡顿。所以有些浏览器针对这种问题,将passive默认值改为true
那么将passive设置为true有什么好处?passive设置为true时,程序会开启两个线程进行处理滚动的问题,一个线程是处理listeners的执行,一个线程是处理执行默认行为,所以正是因为这个原因,MDN文档上指出“passive优化改善滚屏性能”的概念。
下面的例子,成功的取消了touchstart事件的默认行为。注意首先我们说以上测试的结果都是在chrome浏览器中,其次我们需要掌握的不是如何取消touchstart的默认行为,而是passive为什么能够改善滚屏性能的原因?最后要了解addEventListener()方法中的options参数。

window.addEventListener('touchstart',function(e){
	e.preventDefault();
	console.log('Listening scroll....');
	console.log(e.defaultPrevented); // false
},{
	passive:false
});

image.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1618023.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ROS1 驱动USB摄像头 2024年亲测

安装 查看官网文档A ROS Driver for V4L USB Cameras 里面提供了github链接&#xff0c;链接如下&#xff0c;这里要选择develop分支 将这个文件包放到你的工作空间的src目录下&#xff0c;然后回到工作空间编译catkin_make 此时报错no package libv4l2 found 参考stack ov…

SpringCloud知识01

1、数据库创建选择 2、定时任务的实现方式 3、分片原理 4、框架图 5、XXL-Job报错Logback configuration error detected 解决&#xff1a; &#xff08;1&#xff09; <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-bo…

莫名锁表? --- mysql的事务隔离级别

前言 系统响应超时 系统访问数据库特别慢 莫名提示锁等待超时 数据库锁表 事务长时间等锁&#xff0c;直到超时 以上问题都可能是事务锁表导致的 问题 今天测试反馈系统批量处理莫名提示锁等待超时&#xff0c;再次操作查看数据库事务确实存在等锁情况&#xff0c;甚至死锁。…

SpringMVC请求和响应方式

1. SpringMVC的数据响应 1.1 SpringMVC的数据响应方式 页面跳转&#xff1a; 直接返回字符串通过ModelAndView对象返回 回写数据&#xff1a; 直接返回字符串返回对象或集合 1.2 页面跳转 - 直接返回字符串 1.3 页面跳转 - 通过ModelAndView对象返回 1.4 页面跳转 - 传递参数…

ZNS SSD+F2FS文件系统|如何降低GC开销?---1

ZNS出现的背景是什么&#xff1f;ZNS SSD的原理是把namespace空间划分多个zone空间&#xff0c;zone空间内部执行顺序读写。 在ZNS的场景下&#xff0c;不同应用按照Zone配置信息&#xff0c;相应存放业务数据。由于是Host管理数据的摆放和存取位置&#xff0c;会最大程度减少G…

制作识货的商品购买页面(注释加讲解)

在制作此页面时运用了浮动&#xff0c;绝对定位&#xff0c;固定定位&#xff0c;相对定位。这些可以让页面整洁美观。 商品购买页面里有很多的商品可大家观看最上面的搜索栏里可以打字下面的&#xff0c;首页&#xff0c;优惠&#xff0c;识物&#xff0c;登录注册都可以进行…

uniapp H5的弹窗滚动穿透解决

目录 方案一 事件修饰符 overscroll-behavior 修饰符 overscroll-behavior 属性 看个案例 兼容 方案二 overflow&#xff1a;hiden 有一层遮罩蒙层覆盖在body上时&#xff0c;当我们滚动遮罩层&#xff0c;它下面的内容也会跟着一起滚动&#xff0c;看起来好像是上面的…

PHP 爬虫如何配置代理 IP(CURL 函数)

在 PHP中 配置代理IP&#xff0c;可以通过设置 CURL 库的选项来实现&#xff0c;代码如下&#xff1a; 当然你要有代理ip来源&#xff0c;比如我用的这个 代理商 &#xff0c;如果想服务稳定不建议找开源代理池&#xff0c;避免被劫持。 <?php // 初始化cURL会话 $ch cu…

苍穹外卖day12 (Apache POI) 数据统计-Excel报表

文章目录 前言一、工作台1.1 今日数据1.1.1 接口设计1.1.2 代码实现 1.2 订单管理接口1.3菜品总览接口1.4 套餐总览接口1.5 订单搜索&#xff08;已完成&#xff09;1.6 各个状态的订单数量统计&#xff08;已完成&#xff09; 二、Apache POI2.1 概述2.2 效果展示 三、导出运营…

数字逻辑电路基础-有限状态机

文章目录 一、有限状态机基本结构二、verilog写一个基础有限状态机(moore型状态机)三、完整代码一、有限状态机基本结构 本文主要介绍使用verilog编写有限状态机FSM(finite state machine),它主要由三部分组成,下一状态逻辑电路,当前状态时序逻辑电路和输出逻辑电路。 有…

越秀北京 梧桐星宸 l理想人生要满配,这届海淀菁英买房主打“一步到位”

一个理想生活住区&#xff0c;装得下万般繁华与生活想象&#xff1b;一个懂包容的户型&#xff0c;能兼容全家所需并赋能成长。这届眼界超前的海淀菁英&#xff0c;买房需求已经从“阶段进阶”变成“一步到位”&#xff0c;对生活资源与生活空间的至高追求&#xff0c;让他们将…

[Flutter3] Json转dart模型举例

记录一下 Android studio plugin -> FlutterJsonBeanFactory 处理json转dart 模型 案例 json字符串, 一个 response的data返回数据 {"code":1,"msg":"\u64cd\u4f5c\u6210\u529f","data":{"list":{"id":"8…

做抖音小店正确起店的方式,新店铺想快速爆单,步骤就这几个

大家好&#xff0c;我是电商笨笨熊 开通了抖音小店&#xff0c;但是店铺一直没有流量&#xff1b; 很多新手玩家进入抖店后都会遇到这样那样的问题&#xff0c;烦恼的事情一大堆&#xff1b; 没关系&#xff0c;今天我们就来聊聊新店铺该怎么快速起店&#xff0c;新手如何做…

Springboot+Vue项目-基于Java+MySQL的学科竞赛管理系统(附源码+演示视频+LW)

大家好&#xff01;我是程序猿老A&#xff0c;感谢您阅读本文&#xff0c;欢迎一键三连哦。 &#x1f49e;当前专栏&#xff1a;Java毕业设计 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; &#x1f380; Python毕业设计 &…

MLLM | Mini-Gemini: 挖掘多模态视觉语言大模型的潜力

香港中文、SmartMore 论文标题&#xff1a;Mini-Gemini: Mining the Potential of Multi-modality Vision Language Models Code and models are available at https://github.com/dvlab-research/MiniGemini 一、问题提出 通过更高分辨率的图像增加视觉标记的数量可以丰富…

ScanNet 数据集常见文件的作用极其读取方式

ScanNet 数据集是一个大规模的 RGB-D 视频数据集&#xff0c;它包含了丰富的三维场景理解任务所需的数据&#xff0c;如三维物体分类、语义体素标签和 CAD 模型检索等。数据集中的每个文件都有特定的作用&#xff0c;以下是一些常见文件及其作用的解释&#xff1a; _vh_clean.p…

【八股文】Spring 谈谈你对AOP的理解

AOP AOP(Aspect-Oriented Programming&#xff0c;面向切面编程)&#xff1a;是一种新的方法论&#xff0c;是对传统 OOP(Object-Oriented Programming&#xff0c;面向对象编程)的补充。 面向对象是纵向继承&#xff0c;面向切面是横向抽取。 OOP思想是一种垂直纵向的继承体…

C# DataSet结合FlyTreeView显示树状模型数据

目录 关于 FlyTreeView DataSet 数据准备 涉及表结构创建脚本 范例运行环境 方法设计 代码实现 方法代码 调用示例 小结 关于 FlyTreeView NineRays.WebControls.FlyTreeView 是 9rays.net 推出的一款功能强大的树状模型数据显示控件&#xff0c;本文将介绍使用其 As…

运营高手都在用的9款办公软件!一定要收藏

最近&#xff0c;运营群里的00后天天都在搞新花样&#xff0c;每天都有新的idea&#xff0c;各种跟热点、做品牌联名、拍好玩的视频、做创意海报……。但奇怪的是&#xff0c;工作量增加了、业绩增长了&#xff0c;却不见有人加班。一问&#xff0c;原来因为用上了办公神器啊&a…

通过使用XShell工具、Nginx环境实现服务器项目构建与发布

前言&#xff1a; 在信息化和数字化的今天&#xff0c;网站和应用的构建与发布已成为企业发展的重要一环。为了确保项目的顺利上线和稳定运行&#xff0c;选择合适的工具和环境至关重要。本文将详细介绍如何通过XShell工具以及Nginx环境来实现服务器项目的构建与发布&#xff0…