闭包

其实在《有用但不愿意使用的机制 — JavaScript 作用域》一文中,不经意地使用了闭包特性。反过来,闭包最大的特点是可以实现模块功能。

当函数可以记住并访问所在的词法作用域,即使函数是在当前词法作用域之外执行,这时就产生了闭包。
<button>A</button>
<button>B</button>
<button>C</button>
<button>D</button>
<p id="output"></p>
var buttons = document.querySelectorAll('button');
var output  = document.querySelector('#output');

for (var i = 0; i < buttons.length; ++i) {
    buttons[i].addEventListener('click', function (e) {
        output.innerText = buttons[i].innerText;
    }, false)
}

看似会正常运行的代码,居然在点击后出乎意料地报错了

Uncaught TypeError: Cannot read property 'innerText' of undefined
  • Click 事件触发时 i 变量已是 4
  • 循环时 i 变量存在于全局作用域
  • 运行时的监听器读取循环结束后的变量 i (4)

顺利运行

创建回调函数

<button>A</button>
<button>B</button>
<button>C</button>
<button>D</button>
<p id="output"></p>
var buttons = document.querySelectorAll('button');
var output  = document.querySelector('#output');


buttons.forEach(function(ele) {
  ele.addEventListener('click', function (e) {
      output.innerText = e.target.innerText;
  }, false)
})

JavaScript 的数组被赋予 forEach 等方法,它们需要传入回调函数来接受每个循环元素,作为循环体以执行。

这样很好地解决了 for 循环时的作用域问题。

使用 let 关键字

对于原来的代码,只需要改动一个地方即可,将对计数器 i 的定义从 var 改成 let,便可以在循环体内形成块级作用域,让每一次循环的执行都能保留当前计数器的数值和引用。—— 《实战 ES2015》
for (let i = 0; i < buttons.length; ++i) {
    buttons[i].addEventListener('click', function (e) {
        output.innerText = buttons[i].innerText;
    }, false)
}

使用 IIFE

使用立即执行函数包裹,解决闭包问题。

for (var i = 0; i < buttons.length; i++) {
    (function (index) {
        buttons[i].addEventListener('click', function (e) {
            output.innerText = buttons[index].innerText;
        }, false)
    })(i);
}

曲线救国

既然 i 变量有问题,为何不使用回调函数中的参数 e ,这样就可以读取按钮中文本了。

for (var i = 0; i < buttons.length; i++) {
    buttons[i].addEventListener('click', function (e) {
        output.innerText = e.target.innerText;
    }, false)
}

对例子的反思

以上的代码,尝试用点击事件来说明作用域问题,使人处于一个可运行,又可能会出问题的迷茫中。

使用另外一个例子来说明

for(var i = 1; i <= 5; i++) {
   setTimeout(function() {
       console.log('Value of i : ' + i); 
   },100);
} 
Value of i : 6
Value of i : 6
Value of i : 6
Value of i : 6
Value of i : 6

问题便显而易见,使用上面的 let 关键字 或 IIFE 即可解决这个问题。

标签: none

添加新评论