在ul中,点击其中一个标签,弹出或输出相应的序号或相应文本内容

ul中有li,点击li,获得对应的序号

Happy coding…

作为一个前端开发者,一定会遇到这样的一个问题:

一个

    中里面嵌套了好多空的
  • 标签,点击
  • 弹出它的序号(HTML不可以修改)。即点击第一个
  • 标签时弹出0,点击第二个
  • 标签时弹出1。HTML的结构如下:

<ul id="zhuo">
    <li>1li>
    <li>2li>
    <li>3li>
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) // 假装没有被注释
  1. for循环时,我们给每一个li注册了一个click事件,每一个li被点击的时候应该弹出它的序号i
  2. 然而,事实是当你点击li的时候,for循环早已经执行完,已经给每个li注册了事件,事件的绑定是异步操作!
  3. 此时,for循环已经遍历完,i的值已经变成li.length(问题就出现在这里)
  4. 所以,等到你再去点击li时,弹出的是3,而不是它对应的序号
  5. 造成这个问题的根本: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的指向太难理解?你怎么就这么难伺候叻。。。

好吧,我再和你说一个“爸爸给儿子帮忙”的做法:

  1. 你(li)小时候,想带个玩具(index)去跟小朋友玩(炫耀);
  2. 你突然就发现,那个玩具被你上次扔到衣柜上去了,你又拿不到;
  3. 于是,你会大声叫爸爸(ul)/大舅(div)/爷爷(body
  4. 然后,你爸/大舅/爷爷听到你的声音后,就会过来问你,发生什么事(打一顿)?
  5. 接着,他知道是你想叫他过来拿这个玩具,好出去玩
  6. 他给你拿下来后,你就屁颠屁颠(屁股上两巴掌印)地出去玩了

这一整个流程就是,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都没升级到最新版的人交朋友
// 再见!

都看完了,也不评论一下,点个赞吗?

觉得写得不错的同学,可以到我的codepen逛逛噢。

你可能感兴趣的:(JavaScript)