参考资料:https://www.bitovi.com/blog/a-crash-course-in-how-dom-events-work
事件
事件监听
1.怎样监听一个事件
DOM0
1 | <button onclick="alert('hello');"> |
这种直接将事件混合在HTML中,非常的不灵活
DOM1
1 | var button = document.getElementById('button'); |
这种方式相对于前一种,分离了HTML和JavaScript,但是只能为一个元素绑定事件
DOM2
1 | var button = document.getElementById('button'); |
通过addEventListener
可以让我们对事件写更多的处理程序,下面会说addEventListener
的第三个参数(可以控制事件在冒泡阶段还是捕获阶段触发)。
2.事件在文档中怎么穿梭
1 |
|
我们为根元素,body,div,a添加监听事件,并且addEventListener
第三个参数值为false(默认为false),handler在冒泡阶段调用。
点击a之后看到打印出a => div => body => html
修改一下addEventListener
第三个参数值为true,handler在捕获阶段执行
点击a之后看到打印出html => body => div => a
3.浏览器做了什么(用代码来表现事件在浏览器中的行为)
1)element.addEventListener
1 | HTMLNode.prototype.addEventListener = |
addEventListener接受3个参数,事件名,回调,阶段,如果this.handlers不存在,就创建一个对象。如果这个对象没有对应事件的value,设置这个对应事件的value为{capture : [], bubble: []},然后判断phase,将handler压进对应的数组。
2)事件是怎样被调用的
1 | Handle = function(ev){ |
通过上面代码伪实现可以看到stopPropagation在捕获和冒泡阶段都有起作用。
总结
1.element.addEventListener方法,会判断第三个参数phase(默认是false),如果是true就声明(或者向已声明的)capture数组里push进函数。如果是false就向bubble数组内push进函数
2.假设监听的是click事件,在点击目标元素的时候,计算路径,由内到外的获取节点并且push到elements数组里,反转elements数组(变成从外到内),然后遍历elements数组(如果有调用stopPropagation就跳出循环),遍历elements中每个节点的capture数组,并且调用。
3.调用被点击元素的DOM1事件,再次反转elements数组,变成从内到外。
4.遍历elements数组的,遍历每个节点的bubble数组,并且调用。
5.如果没有调用preventDefault方法,就是调用标签的默认行为(比如a标签的跳转)。
总结之后发现漏了 事件代理,赶紧补一下
其实说的比较简单就是:有100个li元素,我点击了某个li元素,于是冒泡到了ul,触发了ul的监听事件,然后在这里再对点击事件做处理。
直接写🌰吧
1 |
|
其实我们做的事情就是为li
添加了自定义属性,然后点击的时候冒泡,打印出被点击的元素的自定义属性。
我们分别点击1到6,看到控制台打印出了自定义的id
嗯,大概就这样子,那么它的有优点是:
首先,每个函数都是对象,我如果为每个li
都添加的话,如果数量很多的话,会占用很多内存,影响性能。然后,网上很多人说处理程序越多,需要不断的对DOM操作,会导致浏览器不断的回流和重绘,其实这点我觉得像之前的这种,点击某个li,大概就是改个颜色或者改一下li的内容,应该和冒泡到ul之后再处理是差不多的,这点还没能很好的理解,之后理解了再来改。
缺点:它是利用冒泡,如果有一些元素没有冒泡,比如input。