js事件、事件流以及target、currentTarget、this那些事
你是如此简单我却将你给遗忘
前面面试被问到js的事件机制 target、currentTarget。碰巧今天有时间来拔一拔,顺便记下。😊
其实要讲清楚target、currentTarget,我们就不得不说一下事件流这个概念,而要言明事件流,我们也要知道一下神马是事件以及一些周边生态概念,这三个概念是必须放在一块说,我们才能对能对这部分的知识有很好的理解滴。大牛略过,如我般的白菜们阅读完本文你将可以了解如下概念:
神马是事件,事件三种写法html事件处理程序、dom0级、dom2级
事件流是什么鬼及其运用和使用上的坑~
target、this、currentTarget的区别
事件
我们先聊聊事件,首先我们必须明确一点,事件通常与函数挂钩。要聊事件这个名词,我先扯下js,我们都知道js由ECMA、DOM、BOM组成,ECMA为js的基本语法,如果你实在不知道是啥,可以简单的理解为他规定了循环是用for关键字,而采用var来定义一个变量,诸如此类;BOM是浏览器对象模型的缩写,即用我们的js来操作浏览器;DOM是文档对象模型的缩写,那啥子是文档呢?简单可以这样理解——我们的html标签在被浏览器渲染的时候会生成一棵标签树,我们可以把这颗由标签构成的树别名称之为文档树或者文档,而每一个标签在js中都可以被抽成一个对象,so,到此你应该已了然于胸了吧?原来文档对象模型可以简单的理解为html标签文档在js中的对象映射,也就是有了dom这个概念,我们可以在js中操作html的元素,如图所示:
<html>
<head>
</head>
<body>
<div>
</div>
</body>
</html>
当我们在源文件中写下如上的代码,浏览器会解析成如图所示
如果把浏览器当作最顶层容器,抽象成文字可以理解为
window-》document-》html-》head/body-》其他元素
这个层级关系就可以理解为文档树了,现在我们可以得出如下几个概念:
- 凡出现在html中的标签被浏览器解析的时候都会被挂载到dom树上。
- dom树上的每个标签都是一个独立的对象,既然是对象就会有属性和方法,上文也提到过事件通常与方法不离不弃,所以这里我们就会get一个基本常识了,事件一般会和标签和方法挂钩,嗯是的!我们通常在标签上注册一个事件,当事件满足条件被触发以后就会执行我们早些预定义好的方法。
- 事件的概念就是:让html在某些特定条件下执行了一段js脚本,某些特定的条件就是如单击,双击,鼠标经过等,这些条件以属性的形式呈现在html代码中,而触发执行的js脚本则通常为方法,如代码:
<div onclick = "fn()"></div>
<script>
funcrion fn(){
alert("我被单击了");
}
</script>
onclick为某些条件的事件句柄,fn为事件满足条件后要执行的函数,以上只是事件的一种写法,不足以应付开发,为此我们还需要了解下常见事件的几种写法:
这里我们需要了解三种,分别为最原始的html混合事件写法,dom0级写法、dom2级写法:
- 最原始的写法:和html混合在一起写,缺点是代码高冗余,且无法添加多个事件处理函数如上文对事件的举例则为典型的**html事件处理程序*写法
-
dom0级:将html代码和js代码分离,且支持匿名函数,可以看到完美的改进了1的冗余缺憾,所有的事件相关操作都在js中完成
//html代码 <div id="a"></div> //js代码 var div1 = document.getElementById("a"); a.onclick=function(){ alert("我被单击了"); }
-
dom2级:ie使用attachEventListener其他非ie使用addEventListener,可以支持绑定多个事件,瞧吧,又一个缺憾被完美解决了~,而且dom2级还可以自定义事件流。
-
dom3级:对事件进行了更广而全的分类,请大家自行查阅。
事件流
接上,事件我们已经知道是个什么鬼了,现在多了个流,总有点排队方向的感脚,没错,很正确的第n感。事件流官网定义:描述的是从页面中接收事件的顺序,那~~~是谁从页面接收事件的顺序呢?接收的顺序有啥子呢?接收的范围呢?
咳咳,回答一下上边的问题呗,谁接收?你说呢?一点头绪还没有的童鞋建议重新看下上文吧,接收的顺序和范围,噗,这是接下来要说道说道的了:
接收顺序(事件流方式),需要我们了解:
1. 事件捕获:什么叫捕获,其实不用扯那么多一句话从最不具体的到最具体的
2. 事件冒泡:什么叫冒泡,正好和上面相反,从最具体的到最不具体的
3. 为什么会有两种事件流方式呢?历史原因,ie提出的是事件冒泡,而w3c提出的是事件捕获。
4. 现代浏览器高级了,那。。。。。嗯,我知道你要问啥,你一定要问浏览器内部是如何解析这两种事件流的,它的执行顺序:事件捕获-》目标阶段-》事件冒泡,一句话就是先捕获后冒泡
捕获和冒泡的范围呢?也就是接收事件的范围呢? 哈哈,这个上面其实我已经说过了:
window-》dowument-》html-》head/body-》div或其他元素
注意昂,现在的浏览器都是从window开始滴,不管冒泡还是捕获其的传递范围一定是父子关系,也就是外层到内层,或者从内层到外层。假设我们给div添加一个事件方法,如果是事件捕获的话是从window开始,因为他是最不具体的元素,如果是冒泡的话则是从div开始,所以这里的具体不具体是相对于添加事件的元素而言的,说到这里,大家一定要知道,我们要使用事件流通常要配合dom2级事件去写。还要知道的一点我们现在开发中多使用的事件流通常是冒泡,嗯这点随了ie了,但是要注意现在的主流浏览器默认就是冒泡,但通常我们在使用addEventListener这个方法的时候他的第三个参数还是会显示的指定为false,即为不捕获**。
防着有人不理解,我多解释几句呗,捕获和冒泡的方式决定着浏览器何时去处理这个事件,如果我把事件声明在冒泡阶段处理,那么在捕获阶段即使捕获到了这个事件,事件也是不会被执行滴,这就是他的一个重要运用,这点也是经常被大家混淆和忽略的地方,但恰好也是它最好玩的地方。随意给大家从网上找两张万年错且没什么卵用的图,说他错是因为现在的事件范围是从window开始的,结果网上的很多图都是画到了document,如下图:
冒泡流程图
捕获流程图
大家看如下例子:
<div id="one">
<div id="two">
<div id="three"></div>
</div>
</div>
one.addEventListener('click',function(e){
console.log('one');
},false);
two.addEventListener('click',function(e){
console.log('two');
},false);
three.addEventListener('click',function(e){
console.log('three');
},true);
使用的是dom2级中的非ie方法,从代码中可以看到one和two为冒泡,three为捕获,所以事件的执行结果就会被改。当我们点击最里面的div的时候,打印数序应该为three->two->one。一句话总结:现代的浏览器对事件的处理方式为先捕获后冒泡,只要有事件就会按这个顺序执行。dom2级决定的只是在事件流的哪个时机去执行处理函数而已而已。
温故而知新啊~上文说到ie使用attachEventListener其他非ie使用addEventListener,且dom2级可以添加多个事件,我们都知道ie使用的是是冒泡也就是从具体到不具体,而非ie为捕获也就是从不具体到最具体,当我们要绑定多个事件处理函数的时候,事件处理函数的顺序ie和非ie在默认情况下是不一致的,因为他们两个的事件流默认情况下是正好相反的,这在不指定事件流参数的情况下确实是个坑~
target、this、currentTarget的区别
现在终于到了target、this、currentTarget
先诉重点理论:
1. target:触发事件的某个具体对象,只会出现在事件流的目标阶段(谁触发谁命中,所以肯定是目标阶段)
2. currentTarget:绑定事件的对象,恒等于this,可能出现在事件流的任意一个阶段中
3. 通常情况下terget和currentTarget是一致的,我们只要使用terget即可,但有一种情况必须区分这三者的关系,那就是在父子嵌套的关系中,父元素绑定了事件,单击了子元素(根据事件流,在不阻止事件流的前提下他会传递至父元素,导致父元素的事件处理函数执行),这时候currentTarget指向的是父元素,因为他是绑定事件的对象,而target指向了子元素,因为他是触发事件的那个具体对象,如下代码和截图所示:
<div id="one">
<div id="three"></div>
</div>
one.addEventListener('click',function(e){
console.log(e.target); //three
console.log(e.currentTarget); //one
},false);