做者 混元霹雳手-Ziksangjavascript
在于前端面试,你给面试官讲一些官方名词,我知道react,vue,angular等等,一系列牛B的框架,对于面试来讲并无卵用,听多了!!有些有是报着真诚的找工做的态度,有些人只是想面面如今的水平如何,可是我想你们确定都想在面试官面前秀一把,讲一讲底层接近原生的东西才叫牛B,我记得我有一个java的同事,面试的时候和我说,面试官问他如何写一个三角型出来,而后他回答,这样回答完我再送一个如何写一个空心三角型,这B装的打满分,一分不扣!!要的就是这个爽感,咱们看题!!前端
<body>
<ul>
<li>0</li>
<li>1</li>
<li>2</li>
</ul>
<script>
var node = document.querySelectorAll('ul li')
for(var i = 0;i<node.length;i++){
node[i].addEventListener('click',function(){
alert('click'+i)
})
}
</script>
</body>复制代码
若是你看这道题,你不用脑子去想,用菊花去想确定,输出是0,1,2
vue
可是你要知道,为何讲面试官都是套路呢?
若是你去面试的时候,面试官问你的每一道题,都要大脑过一下,确定是一些你正常思惟想相反的问题java
可是吧只要有一些做用域基础的同窗来讲,看一眼也就明白什么回事了,var
没有块级做用域导进的最后输出不管你点那个li
,输出的都是3
node
正是由于没有块级做用用域致使,最后循环出来每一个事件输出的都是全局i
,那由于循环跳出结束,最后结果就等于3,这个道理很明白,那咱们怎么去解决这个办法,咱们把这三个办法都玩一下,都是一种渐进行的方法react
从闭包
----> es5 forEach
-----> es6 let
jquery
闭包我总说成包皮es6
对于闭包这种话题我想网上已经说烂了,就是在函数做用域里再声名一个内部做用域,这样因此执行结果拿到的变量都是不一样的,而后就不是拿的全局变量了。面试
var node = document.querySelectorAll('ul li')
for(var i = 0;i<node.length;i++){
(function(i){
node[i].addEventListener('click',function(){
alert('click'+i)
})
})(i)
}复制代码
咱们在绑定事件外层,在循环体内层加了一个自执行函数,你们对这个都不陌生在common.js没有普遍推出来的时候,不管任何框架,为了防止全局变量名的污染,都用这个玩意,此时就行成了一个闭包,每循环一次,都进行一次传参,此时的每一个i都是一个自执行函数体内本身的做用域数组
forEach 操做数组神器
<body>
<ul>
<li>0</li>
<li>1</li>
<li>2</li>
</ul>
<script>
var node = document.querySelectorAll('ul li')
Array.from(node).forEach(function(nodeItem,index){
nodeItem.addEventListener('click',function(){
alert('click'+index)
})
})
</script>
</body>复制代码
这里用forEach
也行成了一个所谓的闭包,forEach
里的执行函数也行成了一个闭包,每一个执行体里,index
都是一局部做用域,那为何用array,from
呢,咱们也能够用[].slice.call(node)
咱们类数组对象转化成真正的数组,由于有些低版本的浏览器不支持摆了,此时,加油,你已经回答了两个方法了,再回答一个es6的方法,虽然如今浏览器不支持,可是咱们通常不会裸用,用bable转化成es5再用
var node = document.querySelectorAll('ul li')
for(let i = 0;i<node.length;i++){
node[i].addEventListener('click',function(){
alert('click'+i)
})
}复制代码
对于2017年之后面试,我相信若是你上面面试的时候你没有写会es6我之后都把闭包写成包皮,如今由于node对es6的支持,大量同窗都开始使用es6,此时对这道题,你不用es6回答出这个问题,我想面试官确定会对你es6这方面直接over,之后咱们可能不再会用var了,由于var会产生太多隐藏问题,不管是变量提高,仍是无块级做用域,也正是var 没有块级做用域致使这个面试题的出来。
面试面到这里我想你们肯想OK,我都回答这么全了,从闭包讲到es5讲到es6还要我怎么样,大多数百分之80的中高级前端工程师,那你如何展示你我的对js更深入的理解呢?,就像我朋友说的那句话,我再送你一个空心三角型
那咱们讲讲事件委托,事件委托是什么鬼东西?事件委托的雏形是由事件冒泡来造成的一个通知链,那咱们看一下什么是事件冒泡
var node = document.querySelectorAll('ul li')
var body = document.querySelectorAll('body')[0]
body.addEventListener('click',function(){
alert('body点击事件行')
})
for(let i = 0;i<node.length;i++){
node[i].addEventListener('click',function(){
alert('click'+i)
})
}复制代码
文档流就是一个dom树,当咱们点击一个元素的时候,会一直向上冒泡事件,当咱们点击Li元素的时候,会向上冒泡,此时,body上的点击事件同时会被执行,那此时就能够衍生出事件代理是什么个回事了,那事件代理又有什么好处呢,此时你应该向面示官展现一下你的拓展行为
1.那此时有99个li元素或者更多的li元素,那给每一个Li元素都绑定点击事件,那咱们启不是先找到ul再找li再循环99次找到对应的事件,此时对性能是一个很大的问题,由于每一个函数都是一个对象,每个对象都有一个占一个内存空间,那咱们起不是要开辟99个内存
1.通常咱们在移动端常常会作一个列表的dropdown,此时咱们确定会往ul里添加更个人Li元素,那此时会发生什么?
<body>
<ul>
<li>0</li>
<li>1</li>
<li>2</li>
</ul>
<button>添加li元素</button>
<script>
var ul = document.querySelectorAll('ul')[0]
var node = document.querySelectorAll('ul li')
var addButton = document.querySelectorAll('button')[0]
var addIndex = 3
addButton.addEventListener('click',function(){
var addli = document.createElement('li')
addli.innerHTML = `这是添加的元素${addIndex}`
ul.append(addli)
addIndex++
})
for(let i = 0;i<node.length;i++){
node[i].addEventListener('click',function(){
alert('click'+i)
})
}
</script>
</body>复制代码
当咱们向li元素里添加新的Li节点击,tmd再点击以后添加的元素没反应,若是咱们用jquery,咱们确定要用到juqery事件代理去解决这个办法,由于对于浏览器对js执行的话,读取js的时候是从上往下读,由于一开始页面咱们没有点击操做js,可是会for循环只绑定了,ul里li元素,由于在初始化就已经对js进行了事件绑定,只是没有执行里面的function执行函数,只有真正点击的时候才会触发。
接下来怎么办,你们确定要看不下去了,讲事件代理,事件代理呢?前面一堆演示费话,在坚持一下,若是你不把原理给搞透,面试搞随便给你一个套路,转一个弯,你就傻B了。
那事件代理就是很简单的一个道理,代理代理,就是经过事件冒泡把所点击的元素代理 在他的父元素上
<body>
<ul> <li>0</li> <li>1</li> <li>2</li> </ul>
<script>
var ul = document.querySelectorAll('ul')[0]
ul.addEventListener('click',function(){
alert('你点击的是Li元素')
})
</script>复制代码
此时很简单的看出咱们经过事件冒泡的原理,把事件代理在父级ul上,因此点击每次Li元素都会出发你点击的是Li元素
那问题来了,上面只是一个冒泡事件的假象,只是利用了冒泡原理作出的一个假象,那们要经过事件代理拿到li元素上的一些信息那咱们该怎么作?
<ul>
<li>0</li>
<li>1</li>
<li>2</li>
</ul>
<script>
var ul = document.querySelectorAll('ul')[0]
ul.addEventListener('click',function(e){
var ev = ev || window.event;
var target = ev.target || ev.srcElement;
if(target.nodeName.toLocaleLowerCase() == 'li'){
alert(target.innerHTML)
}
})
</script>复制代码
这里e
是一个事件对象,能够简称事件源,var target = ev.target || ev.srcElement;
只是对ie版本作了一下兼容,此时点击li,同时会冒泡触发到ul上的事件,能够这么说,li继承了ul上的事件,那此时就是利用事件冒泡Li同时也拥有了ul的事件,此时咱们只要判断当前节点是否是li标签,那就出发当前Li标签的内容
这样改下就只有点击li会触发事件了,且每次只执行一次dom操做,若是li数量不少的话,将大大减小dom的操做,优化的性能可想而知!
若是咱们要对每一个button进行不一样的操做,咱们还能够代理在父节点上不?仍是只操做一次dom,ok没问题
<body>
<div>
<button id="add">添加</button>
<button id="delate">删除</button>
<button id="update">更新</button>
</div>
<script>
var div = document.querySelectorAll('div')[0]
div.addEventListener('click',function(e){
var ev = ev || window.event;
var target = ev.target || ev.srcElement;
if(target.nodeName.toLocaleLowerCase() == 'button'){
switch(target.id){
case 'add':
alert('添加');
break;
case 'delate':
alert('删除');
break;
case 'update':
alert('更新');
break;
}
}
})
</script>
</body>复制代码
咱们判断好节点名好,再进行流程控制语句再根据每一个dom不一样id去判断不一样的操做
那咱们再回来前面的问题,当咱们添加节点的时候,咱们不用事件代理,致使新添加的节点不能执行监听事件,那若是用事件代理可行?,刚刚的一句话,无所不能!!
<body>
<ul>
<li>0</li>
<li>1</li>
<li>2</li>
</ul>
<button>添加li元素</button>
<script>
var ul = document.querySelectorAll('ul')[0]
var addButton = document.querySelectorAll('button')[0]
var addIndex = 3
addButton.addEventListener('click',function(){
var addli = document.createElement('li')
addli.innerHTML = `这是添加的元素${addIndex}`
ul.append(addli)
addIndex++
})
ul.addEventListener('click',function(e){
var ev = ev || window.event;
var target = ev.target || ev.srcElement;
if(target.nodeName.toLocaleLowerCase() == 'li'){
alert(target.innerHTML)
}
})
</script>
</body>复制代码
妈妈不再担忧我喝三路奶粉了,一个代理解决一切,一样的就算你新增仍是删除都不会影响
再请问如今又有一个场景,若是每一个li里面有着其它子节点,好比说ul->li->div-span,那咱们点击任何一个都会触发冒泡事件,那咱们如事件代理,咱们如何准确的定位到Li呢,解决办法-----递归调用
<body>
<ul>
<li id="1">
<span>span元素</span>
</li>
<li id="2">
<div>
<span>div包着一个span元素</span>
</div>
</li>
<li id="3">
<div>div元素</div>
</li>
</ul>
<script>
var ul = document.querySelectorAll('ul')[0]
ul.addEventListener('click',function(e){
var ev = ev || window.event;
var target = ev.target || ev.srcElement;
while(target.nodeName !== ul){
if(target.nodeName.toLocaleLowerCase() == 'li'){
console.log(target.id)
break
}
target = target.parentNode
}
})
</script>
</body>复制代码
我感受很是棒没有毛病 ,双击666666,若是咱们进行一个递归调用,若是是代理节点的元素直接跳出,结束递归,那若是是Li元素咱们就跳出递归,若是咱们点的是Li的任何子元素,继续向上节点查找,直到找到li节点,这很巧妙运用了递归操做来进行事件代理解决一些问题
总结
1减小事件注册,节省内存。好比,在table上代理全部td的click事件。在ul上代理全部li的click事件。
2.简化了dom节点更新时,相应事件的更新。好比不用在新添加的li上绑定click事件。
当删除某个li时,不用移解绑上面的click事件。
1.事件委托基于冒泡,对于不冒泡的事件不支持。
2.层级过多,冒泡过程当中,可能会被某层阻止掉。
3.理论上委托会致使浏览器频繁调用处理函数,虽然极可能不须要处理。因此建议就近委托,好比在table上代理td,而不是在document上代理td。
4.把全部事件都用代理就可能会出现事件误判。好比,在document中代理了全部button的click事件,另外的人在引用改js时,可能不知道,形成单击button触发了两个click事件。
渣渣前端开发工程师,喜欢钻研,热爱分享和讲解教学, 微信 zzx1994428 QQ494755899
支持我继续创做和感到有收获的话,请向我打赏点吧
若是转载请标注出自@混元霹雳手ziksang