口试官提出的题目
我们在口试前端的过程当中,常常会听到口试官问如许的题目:
假如我有一个页面,内里1000个元素都要绑定click事宜,叨教你要如何做
假如你回复逐一绑定那预计能够直接回家了,口试官愿望的答案是你来放言高论事宜托付,你应当能给出要领并写出解决方案。
接下来,考官肯定要问,这么做的优点是什么,或许你为何用事宜托付。
我以为优点主要有两个:
- 事宜只须要绑定一次,而不是绑定1000次,进步效力
- 动态添加进父级的元素自动具有事宜
依据http://www.nczonline.net/blog/2009/06/30/event-delegation-in-javascript/ 更专业的说法以下:
- Fewer functions to manage. 治理较少的函数
- Takes up less memory. 更少的内存斲丧
- Fewer ties between your code and the DOM.下降代码和dom之间的关联
- Don’t need to worry about removing event handlers when changing the DOM via修正dom的时刻不必斟酌删除事宜。
这还没完,口试官一般会接下来问,那末,你绑定在父级上,页面如何晓得你点击的是哪一个?
我想这应当回复,应用了事宜的冒泡机制。
等等,挺在这里,虽然不仅一篇文章论述了事宜托付是应用了冒泡机制,得益于冒泡机制,然则,如何得益的,如何应用的。
因而猎奇的我引伸出别的一个庞杂的议题,事宜的绑定和实行机制。
事宜实行
许多文章里引见了这个机制,这篇文章很简明详实:
http://www.quirksmode.org/js/events_order.html
归纳综合一下,就是由于汗青缘由,浏览器对事宜的处置惩罚有两种形式,一种是先实行表面父级元素的事宜(捕捉形式 capturing),一种先实行内部元素的事宜(冒泡形式bubbling)。这个看法就相似图层,一种是表面的在前面,另一种是内里在前面。
当代浏览器里参照w3c范例,采纳了这两种体式格局并行的体式格局,简朴的来讲就是先捕捉,再冒泡。(为何,为何,规定为一种不好么)
我们都晓得为某个元素注册事宜,是经由过程addEventListener这个要领,那末,我直接注册的事宜,他是属于捕捉实行呢,照样冒泡实行呢?照样说我捕捉阶段实行一次,冒泡阶段再实行一次。难道说每次都实行两次?
不对,一样一次绑定的要领是实行一次,由于它要么属于捕捉阶段,要么属于冒泡阶段。这两个阶段就像通向公司的两条路,你去公司是一条路,返来是别的一条路,你的事宜是路上的小卖铺,它要么在去的那条路上,要么在返来的那条路上。固然你也能够两条路上都开一家小卖铺,虽然说没什么意义,然则如许事宜触发的时刻确切实行了两次,不过这也能证实事宜实行的两个阶段。
事宜实行递次的实验
先忘记上面的理论,下面我们来做个实验,记着下面的html,我们盘算为out同时绑定两个事宜,看看实行递次是如何的
<div id="out">
<div id='inner1'>
Click
</div>
</div>
var a = document.querySelector('div#out')
var b = document.querySelector('div#inner1')
a.addEventListener('click',function(e){
alert(e.target+'事宜A')
})
a.addEventListener('click',function(e){
alert(e.target+'事宜B')
})
我们能够看到,为同一个元素前后绑定两个事宜,实行的递次是从上到下的,把事宜B提到A前面就会先实行B。
接下来我们试一下经由过程addEventListener第三个参数指定事宜绑定在哪条路上,false为冒泡阶段,true为捕捉阶段。
a.addEventListener('click',function(e){
alert(e.target+'外部元素在冒泡阶段')
},false)
a.addEventListener('click',function(e){
alert(e.target+'外部元素在捕捉阶段')
},true)
这下我们能够看到,不管冒泡在前面照样背面,都是先实行捕捉阶段的那行代码,左证了先前说道的w3c范例下的先实行捕捉再实行冒泡的行动。
不过在这里的实验中,我无意发现了一个风趣的征象,当你把html改成没有子元素,比方
<div id="out">
Click
</div>
这时刻就不遵照先捕捉再冒泡的准绳了,看起来像是推断节点没有子元素,就不须要运用捕捉和冒泡的流程,只采纳先来后到的递次。这个中的道理还望高手指教。
总结: 当一个页面元素包括子元素节点的时刻,他在处置惩罚在其身上的绑定事宜的时刻,采纳先实行捕捉阶段的事宜,再实行冒泡阶段的事宜。而事宜处于哪一个阶段,是由addevnetlistener的第三个参数决议的。
阻挠冒泡
我们都晓得,阻挠冒泡是采纳相似 stopPropagation()
的要领。然则请斟酌如许一个题目:
a.addEventListener('click',function(e){
alert(e.target+'外部元素在冒泡阶段')
},false)
a.addEventListener('click',function(e){
alert(e.target+'外部元素在捕捉阶段')
},true)
b.addEventListener('click',function(e){
e.stopPropagation()
alert(e.target+'内部元素上的事宜')
})
这段代码里,我点击b,事宜触发的递次是如何的?
答案是:外部元素捕捉 —> 内部元素事宜
由于捕捉是永久优先实行的,内部元素由于不存在子元素,所以只要一个阶段,无所谓先实行后实行,由于自身没有冒泡事宜,所以stoppropagation()
擦过自身,寻觅父级的冒泡阶段上的事宜,一次查找,悉数给阻挠掉。
所以,想要点击内部的时刻疏忽外部事宜,肯定不要把外部的事宜放在捕捉阶段,就是说第三个参数不要设为true。
我们再把代码搞更庞杂一些:
<div id="out">
<div id="inner1">
<div id="inner2">
click
</div>
</div>
</div>
var a = document.querySelector('div#out')
var b = document.querySelector('div#inner1')
var c = document.querySelector('div#inner2')
a.addEventListener('click',function(e){
alert('a在冒泡阶段')
},false)
a.addEventListener('click',function(e){
alert('a在捕捉阶段')
},true)
b.addEventListener('click',function(e){
alert('b在冒泡阶段')
},false)
b.addEventListener('click',function(e){
alert('b在捕捉阶段')
},true)
c.addEventListener('click',function(e){
alert(e.target+'内部元素事宜')
})
实行一遍,能够加深了对事宜这个形式的明白,递次是如许的
a捕捉 —> b捕捉 —> 内部事宜 —> b冒泡 —> a冒泡
这里我感兴趣的是阻挠冒泡会如何,测下来是,假如把stoppropagation()
放在b,b自身的冒泡照样会实行,那末同理假如放在c,c自身假如有冒泡事宜也会实行,
所以stoppropagation()
所做的事变能够这么明白,阻挠父级元素冒泡阶段的事宜。
事宜托付和冒泡机制有关联吗?
接下来我想引出本文的重点:事宜托付和冒泡机制有关联吗?
我以为就算有关联,关联也不大。
我们先来看一下一个罕见的事宜托付例子:
// Get the element, add a click listener...
document.getElementById("parent-list").addEventListener("click",function(e) {
// e.target is the clicked element!
// If it was a list item
if(e.target && e.target.nodeName == "LI") {
// List item found! Output the ID!
console.log("List item ",e.target.id.replace("post-")," was clicked!");
}
});
简言之,绑定在父类上一个事宜,然后经由过程回调函数的参数取得当前点击的是哪一个元素,相当于把事宜绑定在子元素身上。
叨教这跟上文言简意赅的冒泡机制有什么联络?
假定不存在冒泡或许捕捉,在父类上点击到了子类或许不管点到哪,这个事宜都是要实行的,子类这个元素还会作为援用传到函数体里,我着实看不出这个冒泡有什么关联,假如要什么致使事宜托付能够完成,应当是函数体内的援用才是。
那末,这个援用是什么状况呢,继承上面的实验:
var a = document.querySelector('div#out')
var b = document.querySelector('div#inner1')
var c = document.querySelector('div#inner2')
a.addEventListener('click',function(e){
alert('a在冒泡阶段')
console.log(e.target)//inner2
console.log(this)//out
console.log(e.currentTarget)//out
},false)
a.addEventListener('click',function(e){
alert('a在捕捉阶段')
console.log(e.target)//inner2
},true)
b.addEventListener('click',function(e){
alert('b在冒泡阶段')
console.log(e.target)//inner2
},false)
b.addEventListener('click',function(e){
alert('b在捕捉阶段')
console.log(e.target)//inner2
},true)
c.addEventListener('click',function(e){
alert(e.target+'内部元素事宜')
console.log(e.target)//inner2
})
我们能看到,不管在哪一层里,e.target都是你当前点击的自身,这绝不新鲜,由于e自身是一个event对象,比方这里的MouseEvent,内里还带了是不是同时按下alt键,鼠标位置等信息,可见这个对象自身能够说是和绑定主体无关了,和事宜有关。所以,和冒泡照样没啥关联。以上代码里还展现了两种猎取事宜实行主体的要领,分别是e.currentTarge 和 this
所以我的看法是,虽然提到js的事宜托付一般都邑联络到冒泡,然则就算当初没有设想冒泡和捕捉,事宜托付照样事宜托付,它依靠的是event对象通报到监听函数内里了,和其他无关。