JS/ES6-let应用--踩坑

应用场景描述

在页面中提供了3个按钮,希望在点击按钮时,显示点击的是第几个按钮。刚开始的时候觉得这不是很简单吗?于是三两下写出来下面的代码:

<button>按钮1</button>
<button>按钮2</button>
<button>按钮3</button>
<script>
  window.onload = function () {
    let btns = document.querySelectorAll('button')
    for (var i = 0; i < btns.length; i++) {
      btns[i].onclick = function () {
        console.log(i + 1)
        }
    }
  }
</script>

然后一测试发现无论点击哪一个按钮,结果都是4;明显页面中就只有3个按钮,它整个4出来;为什么会这样呢?这是因为var定义的变量没有块级作用域,所有按钮绑定的点击事件都共享了全局的i变量;在我们点击按钮之前全局变量i因为for循环值已经变成了3;点击按钮后结果自然就是4了。
此时已经发现了要解决的问题就是让每一个按钮绑定的点击事件函数内的i值都是独立、互不影响的;那要如何实现呢?

方法一(将变量设置为属性)

既然每个按钮对应的下标i都需要在事件回调函数中使用,并且根据this的特性,谁调函数this就指向谁(在非箭头函数中);那就可以考虑将i设置为按钮元素对象的属性,在函数中使用this.index去访问,代码如下:

<script>
  window.onload = function () {
    let btns = document.querySelectorAll('button')
    for (var i = 0; i < btns.length; i++) {
      btns[i].index = i
      btns[i].onclick = function () {
        console.log(this.index + 1)
      }
    }
  }
</script>

方法二(利用let的块级作用域)

既然最开始的代码是因为var没有块级作用域才出现问题,那么就可以使用let声明变量来代替var,代码如下:

<script>
  window.onload = function () {
    let btns = document.querySelectorAll('button')
    for (let i = 0; i < btns.length; i++) {
      btns[i].onclick = function () {
        console.log(i + 1)
      }
    }
  }
</script>

在这个方法中,每个按钮绑定的事件函数中都有各自独立的变量i,而且i变量的值是在事件绑定时传入的,存储在函数执行上下文中,事件触发的时候直接从函数执行上下文中获取到i变量的值。

方法三(利用函数的作用域)

在js中函数存在单独的作用域,那么是否可以通过传参从而使得每个点击事件回调函数中的i相互独立呢?于是修改为了下面的代码:

<script>
  window.onload = function () {
    let btns = document.querySelectorAll('button')
    for (var i = 0; i < btns.length; i++) {
      btns[i].onclick = function (i) {
        console.log(i) //MouseEvent {isTrusted: true, screenX: 136, screenY: 132, clientX: 136, clientY: 30, …}
        console.log(i + 1) //[object MouseEvent]1
      }
    }
  }
</script>

但结果似乎出乎意料,正常来讲变量i作为参数传入函数,会在函数执行上下文中存储一个变量i,而且变量i的值是定义事件绑定函数是传入的,而不是触发时传入,这样是可以保证各个按钮的点击事件回调函数中变量i的值是互不影响的,那为什么会出现[object MouseEvent]1的结果呢?通过结果可以知道i的值变为了MouseEvent对象,这表示鼠标事件对象,这是因为在事件回调函数中默认会提供一个参数即事件对象,这个对象的属性中存储了事件触发时一系列信息(如:事件类型、当前鼠标在网页上的横坐标、当前鼠标在网页上的纵坐标等等),而且在事件回调函数中只能传递一个参数,所以但凡传入参数第一个参数赋值为MouseEvent,其他参数赋值为undefined;验证代码如下:

<script>
  window.onload = function () {
    let btns = document.querySelectorAll('button')
    for (var i = 0; i < btns.length; i++) {
      btns[i].onclick = function (ele,i) {
        console.log(ele) //MouseEvent {isTrusted: true, screenX: 136, screenY: 132, clientX: 136, clientY: 30, …}  
        console.log(i) //undefined
      }
    }
  }
</script>

既然回调函数中不能传递参数,我们是否可以将for里面的循环体封装成一个函数呢?

<script>
  window.onload = function () {
    let btns = document.querySelectorAll('button')
    for(var i = 0; i < length; i++) {
      function click(i) {
        btns[i].onclick = function () {
          console.log(i + 1)
        }
      }
      click(i)
    }
  }
}
</script>

通过测试这种方式是可行的,这样每个事件回调函数中都有一个单独的i变量;它们之间的值互不影响。注意:这里没有产生闭包;接下来看一下使用闭包的写法;

方法四(使用闭包)

代码如下:

<script>
  window.onload = function () {
    let btns = document.querySelectorAll('button')
    for(var i = 0; i < length; i++) {
      (function (i) {
        btns[i].onclick = function () {
          console(i + 1)
          }
      })(i)
    }
  }
}
</script>

通过闭包使得每一个事件回调函数中的值为进行函数声明时i变量的值,闭包是在函数声明时产生的,并且闭包中的值不会改变(类似于在事件函数的执行上下文中存储了一个变量)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值