对闭包,我一直都有误解

1 从一个尴尬的例子说起

<ul class="wrap">
  <li>1</li>
  <li>2</li>
  <li>3</li>
</ul>

<script>
  var addEvent = function (nodes) {
    for (var i = 0; i < nodes.length; i++) {
      nodes[i].onclick = function () {
        console.log(i)
      }
    }
  }

  var wrap = document.querySelectorAll('.wrap > li')
  addEvent(wrap)
</script>
复制代码
  • 结果如大家所想,点击每个li,都会打印3

addEvent函数的本意,是想传递给每个事件处理函数:一个唯一的 i 。
而没有按照预期的原因是:事件处理函数绑定的是变量 i 本身,而不是函数在构造时的变量 i 的值。

2 解决

2.1 最常规的解决

利用的是对象属性不变性。

var addEvent = function (nodes) {
    for (var i = 0; i < nodes.length; i++) {
      nodes[i].index = i
      nodes[i].onclick = function () {
        console.log(this.index)
      }
    }
 }
复制代码

2.2 es6

利用的是let声明的变量,仅在块级作用域有效。
i 只在本轮循环有效。所以每次循环的 i 都是一个新的变量。JavaScript引擎内部会记住上一轮循环的值,作为初始化本轮变量 i 的基础。


而对于var,因为声明的变量是全局的,也就是说,使用的是同一个变量。最终在打印输出时,使用的就是全局变量 i ,所以点击时,都是 3。

let addEvent = function (nodes) {
    for (let i = 0; i < nodes.length; i++) {
      nodes[i].onclick = function () {
        console.log(i)
      }
    }
}
复制代码

2.3 闭包(终于到了。。。)

如下所示,事件处理函数中,又返回了一个辅助函数。

  • 因为闭包的特性,
    • 该辅助函数,可以访问他被创建时所处的上下文环境(可以简单理解为:外部函数作用域。因为函数中声明的变量或是传递给函数的变量,是存放在执行上下文中,而执行上下文又挂靠在自己的作用域中。)
    • 该辅助函数,访问的是外部函数中的实际变量,而不是复制后的值。

所以,每次传递给handlers函数的参数 i 都会放到handlers的执行上下文中, 而每次调用函数,都会创建一个执行上下文对象,因为,互不干扰。

var addEvent = function (nodes) {
    var handlers = function (i) {
      return function () {
        console.log(i)
      }
    }
    for (var i = 0; i < nodes.length; i++) {
      nodes[i].onclick = handlers(i)
    }
}
复制代码

3 其他问题

3.1 关于for循环,使用letvar还有区别:

  • let,设置循环变量的那部分,是父级作用域;而循环体内部是单独的子作用域

如下所示,会连续输出3个 a 。首先let是不能重复声明的,但该例中并没有报错。因为第一次循环结束,i++时,i 依旧是 0

for (let i = 0; i < 3; i++) {
  let i = 'a'
  console.log(i)
}
// a
// a
// a
复制代码
  • var,因为通过var声明的是全局变量,并且可以重复声明,所以当 i++ 时,变为了 a++, 则第二次循环时,i 为 NaN,就结束了循环。
for (var i = 0; i < 3; i++) {
  var i = 'a'
  console.log(i)
}
// a
复制代码

3.2 创建函数的问题

  • 要避免在循环中创建函数。

回来最开始的地方,
为了给每个节点都绑定了事件处理函数,在循环中进行绑定。
这样,每次都会创建一个新的匿名函数,带来的只有无味的计算,还容易引起混淆,正如这个例子所示。

var addEvent = function (nodes) {
    for (var i = 0; i < nodes.length; i++) {
      nodes[i].onclick = function () {
        console.log(i)
      }
    }
}
复制代码

转载于:https://juejin.im/post/5b3cac5be51d45191b60f5a4

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值