合规国际互联网加速 OSASE为企业客户提供高速稳定SD-WAN国际加速解决方案。 广告
addEventListener ~~~javascript target.addEventListener(type, listener[, options]); target.addEventListener(type, listener[, useCapture]); ~~~ ## **target.addEventListener(type, listener[, options]);** * options包括三个布尔值选项: * capture: 默认值为false(即 使用事件冒泡). 是否使用事件捕获; * once: 默认值为false. 是否只调用一次,if true,会在调用后自动销毁listener * passive: if true, 意味着listener永远不远调用preventDefault方法,如果又确实调用了的话,浏览器只会console一个warning,而不会真的去执行preventDefault方法。**根据规范,默认值为false. 但是chrome, Firefox等浏览器为了保证滚动时的性能,在document-level nodes(Window, Document, Document.body)上针对touchstart和touchmove事件将passive默认值改为了true**, 保证了在页面滚动时不会因为自定义事件中调用了preventDefault而阻塞页面渲染。 ## **target.addEventListener(type, listener[, useCapture]);** * useCapture: 默认值为false(即 使用事件冒泡)。 * true - 事件处理程序在捕获阶段执行 * false - 默认。 事件处理程序在冒泡阶段执行 ## **DOM的事件传播机制:** DOM事件流(event flow ): 存在三个阶段:事件捕获阶段、处于目标阶段、事件冒泡阶段。 ## **事件冒泡(event capturing) :子传父** 当一个元素接收到事件的时候 会把他接收到的事件传给自己的父级,一直到window 。(注意这里传递的仅仅是事件 并不传递所绑定的事件函数。所以如果父级没有绑定事件函数,就算传递了事件 也不会有什么表现 但事件确实传递了。) 就是事件向上传导,当后代元素的事件被触发时,其祖先元素的相同事件也会触发 例子:点击span,父元素也触发了点击事件 ~~~ <body style="width: 1000px;height: 1000px;"> <div id="info" style="width: 100px;height:100px;background-color: aquamarine;border:5px solid darkred;"> xxx<span id="s1" style="background-color: bisque;">xxx</span> </div> <script type="text/javascript"> window.onload=function(){ var info=document.getElementById("info"); var s1=document.getElementById("s1"); s1.onclick=function(event){ console.log("span单击事件响应函数") } info.onclick=function(event){ console.log("div单击事件响应函数") } document.body.onclick=function(event){ console.log("body单击事件响应函数") } } </script> </body> ~~~ 结果: ~~~ span单击事件响应函数 div单击事件响应函数 body单击事件响应函数 ~~~ 大部分事件冒泡是很有用的,如果不想事件冒泡可以通过事件对象取消事件冒泡 ~~~ <body style="width: 1000px;height: 1000px;"> <div id="info" style="width: 100px;height:100px;background-color: aquamarine;border:5px solid darkred;"> xxx <span id="s1" style="background-color: bisque;">xxx</span> </div> <script type="text/javascript"> window.onload=function(){ var info=document.getElementById("info"); var s1=document.getElementById("s1"); s1.onclick=function(event){ console.log("span单击事件响应函数") event=event||window.event; event.cancelBubble=true; } info.onclick=function(event){ console.log("div单击事件响应函数") } document.body.onclick=function(event){ console.log("body单击事件响应函数") } } </script> </body> ~~~ 结果: ~~~ span单击事件响应函数 ~~~ ## **事件捕获(event capturing):父传子** 当鼠标点击或者触发dom事件时,浏览器会从根节点开始由外到内进行事件传播,即点击了子元素,如果父元素通过事件捕获方式注册了对应的事件的话,会先触发父元素绑定的事件 在DOM2级事件流中包含事件捕获和事件冒泡两个阶段。 * 触发事件处理程序时,先是进入事件捕获阶段,事件由外层向内层具体元素传播; * 然后进入事件冒泡阶段,由内层具体元素再向外层进行传播,事件的处理默认是在冒泡阶段。 事件捕获是不能被阻止的,否则定位不到具体的元素。 ``` <div class="parent" onclick="handleParent()"> <div class="children1"> <div class="children2"></div> </div> </div> ``` 当点击父元素parent时,事件进入捕获阶段: Document → html → body → div.parent → div.children1 → div.children2 然后进入冒泡近段并且执行处理方法: div.children2(触发事件) → div.children1 (触发事件) → div#parent(触发事件) → body → html → Document ``` e.stopPropagation();//停止事件传递 e.cancelBubble = true;//取消冒泡 ``` 给父元素绑定事件,触发事件event.target却是子元素。若要拿绑定事件的初始元素,可用event.currentTarget 或者给子元素加上`pointer-events: none;`样式 ## 关于event对象中的currentTarget属性有时候为null这件事 ``` <div id="outer"> <div id="inner"></div> </div> //JavaScript document.getElementById('outer').addEventListener('click', function(event) { console.log('event.currentTarget:', event.currentTarget);//<div id=​"outer">​…​</div> console.log('event.target:', event.target);//<div id=​"inner">​</div> console.log('this:', this);//<div id=​"outer">​…​</div> 注意:若事件处理程序为箭头函数,则this指向window(not strict)或为undefined('use strict') }); ``` 实际上,currentTarget这个属性是一个实时值而不是快照,随着事件冒泡阶段的结束,它将被解引用,这就是它为null的原因。也就是说如果我们在事件处理程序中使用异步代码访问该属性,就会发生这个问题 ``` document.getElementById('outer').addEventListener('click', function(event) { const ct = event.currentTarget; setTimeout(() => { console.log('event.currentTarget:', event.currentTarget)//null console.log('ct:', ct)//<div id=​"outer">​…​</div> }, 0); setTimeout(() => { console.log('event.target:', event.target)//<div id=​"inner">​</div> }, 0); setTimeout(() => { console.log('this:', this)//<div id=​"outer">​…​</div> }, 0); }); ``` #### 如何与removeEventListener 方法合作? removeEventListener的参数与addEventListener类似: > ~~~javascript > target.removeEventListener(type, listener[, options]); > target.removeEventListener(type, listener[, useCapture]); > > ~~~ 在移除之前添加的监听事件时,很显然需要传入同样的type和listener,那第三个参数options和useCapture呢? **只会检查addEventListener的useCapture或options中的capture值。** 例如: > ~~~javascript > element.addEventListener("mousedown", handleMouseDown, { passive: true }); > element.removeEventListener("mousedown", handleMouseDown, { passive: true }); // Succeeds > element.removeEventListener("mousedown", handleMouseDown, { capture: false }); // Succeeds > element.removeEventListener("mousedown", handleMouseDown, { capture: true }); // Fails > element.removeEventListener("mousedown", handleMouseDown, { passive: false }); // Succeeds > element.removeEventListener("mousedown", handleMouseDown, false); // Succeeds > element.removeEventListener("mousedown", handleMouseDown, true); // Fails > > ~~~ #### 是否一定要与removeEventlister成对儿出现? 当DOM元素与事件拥有不同的生命周期时,倘若不remove掉eventListener就有可能导致内存泄漏(保留或增加了不必要的内存占用)。比如在单页应用中,切换了页面后,原组件已经卸载,但其注册在document上的事件却保留了下来,白白占用了内存空间。**所以removeEventlister与addEventListener成对儿出现是best practice,可以避免可能出现的内存泄漏问题。**