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都没升级到最新版的人交朋友
// 再见!
复制代码
都看完了,也不评论一下,点个赞吗?