ul
中有li
,點選li
,獲得對應的序號
Happy coding...
作為一個前端開發者,一定會遇到這樣的一個問題:
一個
<ul>
中裡面巢狀了好多空的<li>
標籤,點選<li>
彈出它的序號(HTML不可以修改)。即點選第一個<li>
標籤時彈出0
,點選第二個<li>
標籤時彈出1
。HTML的結構如下:
<ul id="zhuo">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>
複製程式碼
我們一看到這樣的需求及HTML結構時,第一時間就會想著用for+onclick+console.log
來完成這個事情:
var zhuo = document.getElementById('zhuo')
var li = zhuo.getElementsByTagName('li')
for(var i = 0, len = li.length; i < len; ++i){
li[i].onclick = function () {
console.log(i) // 寫到這裡,心中暗爽:不就這麼簡單嗎?那你這坑就踩得穩穩的,一測試發現
}
}
// 無論你點選哪一個標籤都會輸出:3
// 這是為什麼呢?
// 下面分析一下程式碼。
// console.log(i) // 假裝沒有被註釋
複製程式碼
for
迴圈時,我們給每一個li
註冊了一個click事件,每一個li
被點選的時候應該彈出它的序號i
;- 然而,事實是當你點選
li
的時候,for
迴圈早已經執行完,已經給每個li
註冊了事件 - 此時,
for
迴圈已經遍歷完,i
的值已經變成li.length
(問題就出現在這裡) - 所以,等到你再去點選
li
時,彈出的是3
,而不是它對應的序號 - 造成這個問題的根本:JS在ES6之前,沒有塊級作用域的概念,於是這裡的
i
本應在迴圈結束後就被銷燬,但,這裡並沒有!
沒有塊級作用域,那麼我們給它偽裝一個不就行了?那麼問題來了:{}
並不能代表一個作用域,那麼用什麼來解決這個問題呢?
相信聰明的你已經想到了,用一等公民的function
,函式擁有自己的作用域;
var zhuo = document.getElementById('zhuo')
var li = zhuo.getElementsByTagName('li')
for (var i = 0, len = li.length; i < len; ++i) {
(function (j) {// 什麼?你問我這個是什麼意思?
// 這是一個閉包函式(IIFE),立即執行函式
// 接收i的值儲存在j中,這個j會一直儲存在這個函式作用域
li[j].onclick = function () {
console.log(j)
}
})(i)// 這裡可以看成函式的入口,傳入i
}
複製程式碼
什麼?你說這個太燒腦?那好吧,我講演示個簡單的:
var zhuo = document.getElementById('zhuo')
var li = zhuo.getElementsByTagName('li')
for (var i = 0, len = li.length; i < len; ++i) {
li[i].index = i // 因為這個li是一個dom物件,所以我們可以在它上面新增屬性
li[i].onclick = function () {
var index = this.index // 這裡的this指向li[i],因此我們可以從上面取到之前的index屬性值
console.log(index)
}
}
// 終於寫完了....
複製程式碼
納尼?你說,this
的指向太難理解?你怎麼就這麼難伺候叻。。。
好吧,我再和你說一個**“爸爸給兒子幹活”**的做法:
- 你(
li
)小時候,想帶個玩具(index
)去跟小朋友玩(炫耀); - 你突然就發現,那個玩具被你上次扔到衣櫃上去了,你又拿不到;
- 於是,你會大聲叫爸爸(
ul
)/大舅(div
)/爺爺(body
) - 然後,你爸/大舅/爺爺聽到你的聲音後,就會過來問你,發生什麼事(打一頓)?
- 接著,他知道是你想叫他過來拿這個玩具,好出去玩
- 他給你拿下來後,你就屁顛屁顛(屁股上兩巴掌印)地出去玩了
這一整個流程就是,DOM中的冒泡機制(1,2,3)與捕獲機制(4,5,6),這也就是我們接下來要說的委託事件原理。
var ul = document.getElementById('zhuo')
ul.onclick = function(event){
event = event || window.event // 額...理解不了的話,你當這個是一個DOM物件
target = event.target // 獲得點選的最底層DOM
if(target.nodeName === 'LI'){// 判斷這個DOM節點名字是不是li,
console.log(target.innerHTML)// 好了,被你發現了;這裡不是彈出序號,是內容。
}
}
// 聰明的同學一定看出來了,這樣的寫法更簡潔,不需要for迴圈,不需要給每個li繫結click事件
// 這樣做的好處多多
// 誒誒誒,你別走啊,都看到這裡了,接著看下去唄。
// 下面的辦法更簡單
複製程式碼
好了,看到這裡,你會疑惑為什麼是ES6之前沒有,難道現在就有了?
沒錯,在前端開發的歷(ku)史(bi)長(ri)河(zi)中,開發者們越來越覺得不對勁,於是就在ES6加入了牛逼哄哄的let
;
let
到底是何方神聖,為什麼博主在這裡說它?他?她?牛逼呢?
let
允許你宣告一個作用域被限制在塊級中的變數、語句或者表示式。與var
關鍵字不同的是,它宣告的變數只能是全域性或者整個函式塊的。
也就是說,用這個let
,我們就可以很nice地解決上面提到的問題了
var zhuo = document.getElementById('zhuo')
var li = zhuo.getElementsByTagName('li')
for(let i = 0, len = li.length; i < len; ++i){
li[i].onclick = function () {
console.log(i) // 噹噹噹,是不是覺得So easy?
}
}
// 什麼?你的開發環境不支援ES6的語法?
// 告辭!打擾了
// 我不跟chrome都沒升級到最新版的人交朋友
// 再見!
複製程式碼
都看完了,也不評論一下,點個贊嗎?