javascript事件机制底层完毕原理,前端盲点

前言

很久没有扯淡了,大家前几天来聊聊吧。

自家前日思想了二个难题,我们页面的dom树到底是何等渲染的,而CSS盒模型与javascript是不是有关系,于是便想到贰个难点:

CSS的盒模型具有厚度么???

该文只是一种辅助明白的传教,与官方概念不必然统一,权当扯淡

前言

又到了拉家常时间了,我近年在思考javascript事件机制底层的兑现,但是目前没有勇气去看chrome源码,所此前天自个儿来猜忌一把

我们后天来猜一猜,切磋琢磨,javascript底层事件机制是什么贯彻的

盒模型

稍许入门点的前端都精通CSS盒模型,于是我们耐心的偷图来用:

图片 1

图片 2

以此就是大家故事中的盒模型,大家那边先把盒模型放一放,来看我们的DOM事件流

基础知识

DOM事件流

在上一篇博客中,大家详细说了下javascript的风浪机制:【移动端包容问题切磋】javascript事件机制详解(涉及移动包容)

今天也不知道怎么发神经了,突然对里面多个事件参数发生兴趣:

事件捕获/冒泡

大家点击三个span,作者说不定就想点击壹个span,事实上他是先点击document,然后点击事件传递到span的,而且并不会在span停下,span有子元素就会继续往下,最终会挨个回传至document,大家那边偷一张图:

图片 3

我们那边偷了一张图,那张图很好的求证了事件的传播情势

事件冒泡即由最具体的元素(文档嵌套最深节点)接收,然后逐步上传至document

事件捕获会由最先接收到事件的元素然后传向最里边(我们可以将元素想象成一个盒子装一个盒子,而不是一个积木堆积)

此处大家进入dom事件流,那里大家详细看看javascript事件的传递形式

currentTarget

某事件处理程序当前正值处理的非常成分

DOM事件流

DOM2级事件规定事件包括五个阶段:

① 事件捕获阶段

② 处于目标阶段

③ 事件冒泡阶段

target

事件目标(绑定事件分外dom),源事件触发点

 

那三个弟兄格外有意思,为了验证她的诙谐,作者写了一段代码:

 1 <html xmlns="http://www.w3.org/1999/xhtml">
 2 <head>
 3     <title></title>
 4     <style type="text/css">
 5          #p { width: 300px; height: 300px; padding: 10px;  border: 1px solid black; }
 6          #c { width: 100px; height: 100px; border: 1px solid red; }
 7     </style>
 8 </head>
 9 <body>
10     <div id="p">
11         parent
12         <div id="c">
13             child
14         </div>
15     </div>
16     <script type="text/javascript">
17         var p = document.getElementById('p'),
18         c = document.getElementById('c');
19         c.addEventListener('click', function () {
20             alert('子节点捕获')
21         }, true);
22 
23         c.addEventListener('click', function () {
24             alert('子节点冒泡')
25         }, false);
26 
27         p.addEventListener('click', function () {
28             alert('父节点捕获')
29         }, true);
30 
31         p.addEventListener('click', function () {
32             alert('父节点冒泡')
33         }, false);
34     </script>
35 </body>
36 </html>

http://sandbox.runjs.cn/show/ij4rih6x

以此东西其实是丰盛幽默的,根据我们当即的逻辑:

图片 4


点击parent,事件首先在document上然后parent捕获到事件,处于目的阶段然后event.target相当于parent,所以触发捕获事件

由于target与currentTarget相等了,所以觉得到底了,初阶冒泡,执行冒泡事件


点击child情状有所不一致,事件由document传向parent执行事件,然后传向child最后开首冒泡

上面的诠释没难点,我们许多有情人也都清楚,可是自己前几天突然难点就来了,CSS盒模型尼玛到底是怎么排布的???

事件目的

所谓事件目的,是与一定目的相关,并且包涵该事件详细消息的目的。

事件目标作为参数传递给事件处理程序(IE8在此之前经过window.event得到),全部事件目标都有事件类型type与事件目的target(IE8以前的srcElement我们不爱戴了)

次第事件的轩然大波参数不平等,比如鼠标事件就会有连锁坐标,包涵和开创他的特定事件有关的习性和格局,触发的事件差别,参数也不一样(比如鼠标事件就会有坐标新闻),大家那里题多少个较主要的

PS:以下的男生儿全体是只读的,所以不要妄想去随意改变,IE此前的题材大家就不关切了

猜想一:积木式=盒模型

先是,也是最简单易行的猜度,盒模型就是堆积木(其实依照上图来看,那一个想法大势所趋)

图片 5

这一个想法不难,而且直观,也和大家一般的思辨类似,但是难点当即来了:

bubbles

注解事件是或不是冒泡

事件捕获会先于事件冒泡暴发

事件捕获:由最先接收到事件的元素然后传向最里边

就算是汉语翻译,大家得以拿走五个紧要音讯(点击child
div、忽略document):

① parent div 应该先于child div触发

② “最中间”,那个最里面应该怎样知道需求关心!!!

cancelable

表明是或不是可以收回事件的暗许行为

悖谬

遵从上图的精晓,大家那里就该child div事件先触发呢,处境却是parent
div先触发,那与原理不合,所以CSS盒模型不应当是那几个样子

于是乎,小编那里推翻了估算一,那里开展揣测二。

currentTarget

某事件处理程序当前正值处理的老大成分

猜想二:盒子=盒模型

本条推断也比较直接,因为盒模型嘛。。。。。。

图片 6

初期,作者在想盒模型是其一样子,他能很好的演讲为何会先触发parent的click事件而不是child的,可是他仍有1个让作者痛楚的地方:

本身body恐怕parent
div即使有颜色,小编其中的要素岂不是看不到了???那与现实不合啊???

于是乎作者再两次陷入僵局,于是进入推断三,也是最终揣度

defaultPrevented

为true注解已经调用了preventDefault(DOM3新增)

最终幻想:盒模型=具有厚度一面透明盒子

实则用盒子来形容盒模型是尤其适合的,而且

CSS盒模型是具有厚度的

大家那边有二个很好的形容词来形容CSS盒模型:捅不破的XX膜!

PS:那里本来想画3个图的而是,确实画不出来,各位将就点听作者讲述吧,笔者觉着

图片 7

以此相应是大家的盒模型了,由此大家就足以更好的注解我们的答案了:

① 点击parent区域时,咱们先是触发四次点击,流程如下:

document=》body=》parent div=开头冒泡=》parent div=》body=》document

我们因为在parent div上注册了轩然大波,所以她会执行

② 点击child区域时

document=》body=》parent div=》child div=初阶冒泡=》child div =》parent
div=》body=》document

此间景况也正如鲜明了,大家一回点击鼠标会点破透明膜,然后两回会遭逢上边的因素(离开时候会记住碰着的最深dom约等于target)

eventPhase

调用事件处理程序的阶段:1 抓获;2 高居阶段;3 冒泡阶段

其一本性的变型须求在断点中查阅,不然你看来的总是0

底层事件模拟(纯粹幻想)

好了,大家那里来做一次详细的风浪模拟:


首先大家公司我们的dom树,为dom绑定事件(这里绑定事件作者就当向hash中传来了值)

 1 el.addEventListener(type, fn, useCapture);
 2 //每个dom在浏览器中肯定会具有一个唯一标识,我不知道是什么,暂且认为是uuid
 3 //于是上面的代码在浏览器中大概是这个意思
 4 
 5 var hashCapture = {};//保存捕获事件集合
 6 var hashBubble = {};//保存冒泡事件集合
 7 
 8 if (useCapture) {
 9     //装载捕获时的click事件
10     pushSet(el, type, fn, hashCapture);
11 } else {
12     //装载冒泡时的click事件
13     pushSet(el, type, fn, hashCapture);
14 }
15 
16 function pushSet(el, type, fn, hashSet) {
17     hashSet[el.uuid] = {};
18     hashSet[el.uuid][type].push(fn);
19 }

PS:注意,此处完全是一种假想,小编并不知道系统底层怎样贯彻

安份守己上述思想,其实保存了多少个对象:

一个关于捕获时期dom(el)click事件的集合()——hashCapture
一个关于冒泡时期dom(el)click事件的集合()——hashBubble


我们开端点击,然后记录点击进度中,手指遇到的要素(我们那里简化去掉document)

body=》parent div=》child div=》child div =》parent div=》body

 

坚守下边的逻辑,作者先是遇到了body,离开前相遇了child
div,大家最好触碰的dom就是target,所以

1 var Event = {};
2 //捕获时碰到的dom
3 Event.captureDoms = [];
4 //冒泡时候碰到的dom
5 Event.captureDoms = [];
6 Event.target = dom;//最后碰到的元素

之所以地方的target永远都以child div,而currentTarget却会不停变化

captureDoms装的是body,parent div, child div

而captureDoms 与他一致,只可是顺序是反的,最好就只剩余执行事件了

PS:当大家鼠标离开window时,判断为三遍点击,于是:

1 for (var i = 0, len < Event.captureDoms.length; i < len; i++) {
2     var el = Event.captureDoms[i];
3     var uuid = el.uuid;
4     //获取捕获中保存的click事件集合
5     for (var j = 0, len1 < hashCapture[uuid].click.length; j < len1; j++) {
6         hashCapture[uuid]['click'][j](Event);//执行保存的click事件
7     }
8 }

然后实施冒泡时候的一模一样逻辑即可,于是就大家的困惑停止……

target

事件目的(绑定事件尤其dom)

firefox模型

图片 8

图片 9

图片 10

firefox dom结构如此,可是与作者的推断好像差距……

trusted

true声明是系统的,false为开发人士自定义的(DOM3新增)

结语

我们的盒模型具有厚度吗?作者以为是颇具的,而且她很好的演说了自作者遇见的标题,所以自个儿以为他是全数的,作者能自圆其说……

type

事件类型

view

与事件涉及的指雁为羹视图,发生事变的window对象

preventDefault

注销事件暗中认同行为,cancelable是true时可以接纳

stopPropagation

裁撤事件捕获/冒泡,bubbles为true才能采纳

stopImmediatePropagation

注销事件特别冒泡,并且协会其余事件处理程序被调用(DOM3新增)

在我们的事件处理内部,this与currentTarget相同

宪章javascript事件机制

在此以前,大家的话多少个基础知识点

dom唯一标识

在页面上的dom,每种dom都应有有其唯一标识——_zid(大家那里统一为_zid)/sourceIndex,不过多数浏览器或许觉得,这一个接口并不必要告诉用户之所以大家都无法博取

可是IE将以此接口放出去了——sourceIndex

咱俩那边以百度首页为例:

1 var doms = document.getElementsByTagName('*');
2 var str = '';
3 for (var i = 0, len = doms.length; i < len; i++) {
4     str += doms[i].tagName + ': ' + doms[i].sourceIndex + '\n';
5 }

图片 11

图片 12

可以看到,越是上层的_zid越小

其实,dom
_zid生成规则应有是以树的正序而来(好像是吧…..),反正是从上到下,从左到右

有了这些后,大家来看望大家什么赢得七个dom的注册事件集合

取得dom注册事件集合

比如说大家为五个dom同时绑定了一个click事件,又给他绑定3个keydown事件,那么对于这么些dom来说他就颇具二个事件了

小编们有啥样方式可以博得二个dom注册的事件呢???

答案很不满,浏览器都没有自由api,所以大家一时半刻不可以通晓二个dom到底被注册了略微事件……

PS:假若你明白这几个难点的答案,请留言

有了上述三个知识点,大家就可以起来明天的聊天了

只顾:下文进入预计时间

补充点

此处经过园友 JexCheng 的唤醒,其实某些浏览器是提供了拿到dom事件节点的法门的

DOM API是没有。不过浏览器提供了一个调试用的接口。
Chrome在console下可以运行下面这个方法:
getEventListeners(node),
获得对象上绑定的所有事件监听函数。

注意,是在console里面执行getEventListeners方法

 1 <html xmlns="http://www.w3.org/1999/xhtml">
 2 <head>
 3   <title></title>
 4 </head>
 5 <body>
 6 <div id="d">ddssdsd</div>
 7   <script type="text/javascript">
 8     var node = document.getElementsByTagName('*');
 9     var d = document.getElementById('d');
10     d.addEventListener('click', function () {
11       alert();
12     }, false);
13     d.addEventListener('click', function () {
14       alert('我是第二次');
15     }, false);
16     d.onclick = function () {
17       alert('不规范的绑定');
18     }
19     d.addEventListener('click', function () {
20       alert();
21     }, true);
22 
23     d.addEventListener('mousedown', function () {
24       console.log('mousedown');
25     }, true);
26     var evets = typeof getEventListeners == 'function' && getEventListeners(d)
27   </script>
28 </body>
29 </html>

如上代码在chrome中的console结果为:

图片 13

可以看来,无论何种绑定,那里皆以足以拿到的,而且获得的靶子与咱们模拟的对象相比相近

事件注册暴发的事

率先,我们为dom注册事件的语法是:

1 dom.addEventListener('click', function () {
2     alert('ddd');
3 })

以上述代码来说,我当做浏览器,以这一个代码来说,在登记阶段自身便足以保留以下音讯:

 1 <html xmlns="http://www.w3.org/1999/xhtml">
 2 <head>
 3     <title></title>
 4     <style type="text/css">
 5          #p { width: 300px; height: 300px; padding: 10px;  border: 1px solid black; }
 6          #c { width: 100px; height: 100px; border: 1px solid red; }
 7     </style>
 8 </head>
 9 <body>
10     <div id="p">
11         parent
12         <div id="c">
13             child
14         </div>
15     </div>
16     <script type="text/javascript">
17         var p = document.getElementById('p'),
18         c = document.getElementById('c');
19         c.addEventListener('click', function () {
20             alert('子节点捕获')
21         }, true);
22 
23         c.addEventListener('click', function () {
24             alert('子节点冒泡')
25         }, false);
26 
27         p.addEventListener('click', function () {
28             alert('父节点捕获')
29         }, true);
30 
31         p.addEventListener('click', function () {
32             alert('父节点冒泡')
33         }, false);
34     </script>
35 </body>
36 </html>

图片 14

此地,我们为parent和child绑定了click事件,所以浏览器可以拿走如下队列结构:

 1 /****** 第一步-注册事件 ******/
 2 //页面事件存储在一个队列里
 3 //以_zid排序
 4 var eventQueue = [
 5   {
 6     _zid: 'parent',
 7     handlers: {
 8       click: {
 9         captrue: [fn, fn],
10         bubble: [fn, fn]
11       }
12     }
13   },
14   {
15     _zid:'child',
16     handlers:{
17       click: {
18         captrue: [],
19         bubble: []
20       }
21     }
22   },
23   {
24     _zid: '_zid',
25     handlers: {
26     //……
27     }
28   }
29 ];

就那parent那个div来说,大家为她绑定了七个click事件(大家实际上可以绑定三个5个恐怕越多,所以事件集合是一个数组,执行具有先后顺序)

中间注册事件时候,又会分冒泡和破获,而且那里以_zid排序(比如:document->body->div#p->div#c)

接下来首个级次就甘休了

PS:小编想底层c++语言一定有像样的这么些队列,而且可以释放接口,让大家赢得二个dom所注册的享有事件

留意,此处队列是如此,不过我们实在点击贰个成分,大概就只抽取其中有些关乎的目的组成七个新的体系,供上边拔取

初始化事件参数

其次步就是伊始化事件参数,大家可以由此add伊芙ntListener,创制事件参数,可是大家那里大致模拟即可:

留神,为了方便清楚,大家那里暂不考虑mousedown

1 /****** 第二步-初始化事件参数 ******/
2 var Event = {};
3 Event.type = 'click';
4 Event.target = el;//当前手指点击最深dom元素
5 //初始化信息
6 //......
7 //鼠标位置信息等

在此地相比根本的就是大家肯定要优质定义咱们的target!!!

于是乎能够进入我们的关键步骤了,触发事件

接触事件

事件触发分三步走,首先是捕获然后是居于阶段最终是冒泡阶段:

 1 /****** 第三步-触发事件 ******/
 2 var isTarget = false;
 3 Event.eventPhase = 1;
 4 //首先是捕获阶段,事件执行至event.target为止,我们这里只关注click
 5 for (var index = 0, length = eventQueue.lenth; index < length; index++) {
 6   //获取捕获时期该元素的click事件集合
 7   var clickHandlers = eventQueue[index].handlers.click.captrue;
 8   for (var i = 0, len = clickHandlers.length; i < len; i++) {
 9     Event.currentTarget = clickHandlers[i]; //事件处理程序当前正在处理的那个元素
10     //执行至target便跳出循环,不再执行下面的操作
11     if (Event.target._zid == eventQueue[index]._zid) {
12       Event.eventPhase = 2;//当前阶段
13       isTarget = true;
14     }
15     //执行绑定事件
16     clickHandlers[i](Event);
17     //如果阻止冒泡,跳出所有循环,不执行后面的事件
18     if (Event.bubbles) {
19       return;
20     }
21   }
22   //若是当前已经是target便不再向下捕获
23   if(isTarget) break;
24 }
25 Event.eventPhase = 3;
26 //冒泡阶段
27 for(var index = eventQueue.lenth; index !=0; index--) {
28   //如果zid小于等于当前元素,说明不需要处理
29   if(eventQueue[index]._zid <= Event.target._zid) continue;
30   //需要处理的部分了
31   var clickHandlers = eventQueue[index].handlers.click.bubble;
32  
33   //此段代码可以重构,暂时不管
34   for (var i = 0, len = clickHandlers.length; i < len; i++) {
35     Event.currentTarget = clickHandlers[i]; //事件处理程序当前正在处理的那个元素
36     //执行绑定事件
37     clickHandlers[i](Event);
38     //如果阻止冒泡,跳出所有循环,不执行后面的事件
39     if (Event.bubbles) {
40       return;
41     }
42   }
43 }

这一个注释写的很明亮了相应能表达清楚自身的情趣,于是大家那里就大约的效仿了事件机制的最底层原理了:)

PS:就算你认为窘迫,请留言

表明推断

以后,基础理论指出来了,大家必要验证下这个想法是还是不是站得住脚,所以那里提了多少个例证,首先我们回来地方的难题吧

证爱他美(Aptamil):点击难点

http://sandbox.runjs.cn/show/pesvelp1

率先大家来看那几个标题,大家独家为parent与child注册了八个click事件,一回冒泡两回捕获

当大家点击父成分时,我们依据理论的执行逻辑如下:

始发遍历事件队列(由document开首)

当遍历对象借使注册了click事件就会触发,如若阻止了冒泡,执行后便跳出循环不再履行

因为从前并不曾注册事件,所以平素到了parent,这里发现parent的_zid与target的_zid相等

于是便将状态置为处于目的阶段,并打上标记跳出捕获循环,不再实施前面的风云句柄

Event.eventPhase = 2;//当前阶段
isTarget = true;

抓获为止后,起头举行冒泡的事件,循环由后迈入,开首是child的click事件,不过此时child的_zid大于target的_zid所以继续循环

终极会执行parent以上的dom注册的click事件,没有尽管了

至于点击child的逻辑大家那里就不分析了

表达二:突然移除dom

咱俩那边对上题做1个变形,大家在parent点击时候(捕获阶段)将child
div给删除,看看有如何意况

http://sandbox.runjs.cn/show/f1ke5vp8

 1 <html xmlns="http://www.w3.org/1999/xhtml">
 2 <head>
 3     <title></title>
 4     <style type="text/css">
 5          #p { width: 300px; height: 300px; padding: 10px;  border: 1px solid black; }
 6          #c { width: 100px; height: 100px; border: 1px solid red; }
 7     </style>
 8 </head>
 9 <body>
10     <div id="p">
11         parent
12         <div id="c">
13             child
14         </div>
15     </div>
16     <script type="text/javascript">
17         var p = document.getElementById('p'),
18         c = document.getElementById('c');
19         c.addEventListener('click', function () {
20             alert('子节点捕获')
21         }, true);
22 
23         c.addEventListener('click', function () {
24             alert('子节点冒泡')
25         }, false);
26 
27         p.addEventListener('click', function () {
28             alert('父节点捕获')
29             p.removeChild(c);
30         }, true);
31 
32         p.addEventListener('click', function () {
33             alert('父节点冒泡')
34         }, false);
35     </script>
36 </body>
37 </html>

实际上那里还有八个优化点,相信大家都晓得:

移除dom并不会移除事件句柄,这个必须手动释放

就是因为这些缘故,大家的全方位逻辑依旧会履行,各位自个儿可以试行

评释三:child阻止冒泡

大家那里再将上题稍加变形,在child
冒泡阶段协会冒泡,其实这一个不用说,parent的click不会实施

 1 <html xmlns="http://www.w3.org/1999/xhtml">
 2 <head>
 3     <title></title>
 4     <style type="text/css">
 5          #p { width: 300px; height: 300px; padding: 10px;  border: 1px solid black; }
 6          #c { width: 100px; height: 100px; border: 1px solid red; }
 7     </style>
 8 </head>
 9 <body>
10     <div id="p">
11         parent
12         <div id="c">
13             child
14         </div>
15     </div>
16     <script type="text/javascript">
17         var p = document.getElementById('p'),
18         c = document.getElementById('c');
19         c.addEventListener('click', function () {
20             alert('子节点捕获')
21         }, true);
22 
23         c.addEventListener('click', function (e) {
24             alert('子节点冒泡')
25             e.stopPropagation();
26         }, false);
27 
28         p.addEventListener('click', function () {
29             alert('父节点捕获')
30         }, true);
31 
32         p.addEventListener('click', function () {
33             alert('父节点冒泡')
34         }, false);
35     </script>
36 </body>
37 </html>

验证四:模拟click事件

 1 <html xmlns="http://www.w3.org/1999/xhtml">
 2 <head>
 3     <title></title>
 4     <style type="text/css">
 5          #p { width: 300px; height: 300px; padding: 10px;  border: 1px solid black; }
 6          #c { width: 100px; height: 100px; border: 1px solid red; }
 7     </style>
 8 </head>
 9 <body>
10     <div id="p">
11         parent
12         <div id="c">
13             child
14         </div>
15     </div>
16     <script type="text/javascript">
17         alert = function (msg) {
18             console.log(msg);
19         }
20 
21         var p = document.getElementById('p'),
22         c = document.getElementById('c');
23         c.addEventListener('click', function (e) {
24             console.log(e);
25             alert('子节点捕获')
26         }, true);
27         c.addEventListener('click', function (e) {
28             console.log(e);
29             alert('子节点冒泡')
30         }, false);
31 
32         p.addEventListener('click', function (e) {
33             console.log(e);
34             alert('父节点捕获')
35         }, true);
36 
37         p.addEventListener('click', function (e) {
38             console.log(e);
39             alert('父节点冒泡')
40         }, false);
41 
42         document.addEventListener('keydown', function (e) {
43             if (e.keyCode == '32') {
44                 var type = 'click'; //要触发的事件类型
45                 var bubbles = true; //事件是否可以冒泡
46                 var cancelable = true; //事件是否可以阻止浏览器默认事件
47                 var view = document.defaultView; //与事件关联的视图,该属性默认即可,不管
48                 var detail = 0;
49                 var screenX = 0;
50                 var screenY = 0;
51                 var clientX = 0;
52                 var clientY = 0;
53                 var ctrlKey = false; //是否按下ctrl
54                 var altKey = false; //是否按下alt
55                 var shiftKey = false;
56                 var metaKey = false;
57                 var button = 0; //表示按下哪一个鼠标键
58                 var relatedTarget = 0; //模拟mousemove或者out时候用到,与事件相关的对象
59                 var event = document.createEvent('Events');
60                 event.myFlag = '叶小钗';
61                 event.initEvent(type, bubbles, cancelable, view, detail, screenX, screenY, clientX, clientY,
62 ctrlKey, altKey, shiftKey, metaKey, button, relatedTarget);
63                 
64                 console.log(event);
65                 c.dispatchEvent(event);
66             }
67         }, false);
68     </script>
69 </body>
70 </html>

http://sandbox.runjs.cn/show/pesvelp1

作者们最终模拟一下click事件,那里按空格便会触发child的click事件,那里如故走大家上述逻辑

由此,我们前日到此截止

结语

前几日,大家联合模拟预计了javascript事件机制的平底完结,那里只做了最简易最单纯的效仿

例如三个平级dom(div)点击时候那里的算法就有少数标题,不过无伤大雅,商量嘛,至于工作的真面目如何,那里就只可以投砾引珠了。

不错答案要索要看chrome源码了,这么些留待我们前面解答。

假使您对此文中的想法有和看法大概提出,请留言