我认真起来连面试官都怕(块级做用域,事件代理)

做者 混元霹雳手-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,2vue

可是你要知道,为何讲面试官都是套路呢?
若是你去面试的时候,面试官问你的每一道题,都要大脑过一下,确定是一些你正常思惟想相反的问题java

可是吧只要有一些做用域基础的同窗来讲,看一眼也就明白什么回事了,var 没有块级做用域导进的最后输出不管你点那个li,输出的都是3node

正是由于没有块级做用用域致使,最后循环出来每一个事件输出的都是全局i,那由于循环跳出结束,最后结果就等于3,这个道理很明白,那咱们怎么去解决这个办法,咱们把这三个办法都玩一下,都是一种渐进行的方法react

闭包 ----> es5 forEach -----> es6 letjquery

闭包我总说成包皮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事件。

仔细思考!!!从这我发现了,若是不讲事件代理这回事,可能你们都认为事件冒泡只是触发了上级事件,否则,我以为这说的不彻底,若是只是触发上层事件的话,那咱们点击li如何拿到ul所进行的操做方法,应该说,向上冒泡先出发上级事件,而后再继承上级事件。就是简简单单的触发!!若是我说的有错的话,大神们能够随时喷

渣渣前端开发工程师,喜欢钻研,热爱分享和讲解教学, 微信 zzx1994428 QQ494755899

支持我继续创做和感到有收获的话,请向我打赏点吧

若是转载请标注出自@混元霹雳手ziksang

相关文章
相关标签/搜索