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