我認真起來連面試官都怕(塊級做用域,事件代理)

做者 混元霹靂手-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

相關文章
相關標籤/搜索