Mather

We create our own demons.

混乱作用域、闭包问题

默认分类 0 评

<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)

顺利运行

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 即可解决这个问题。

Newifi mini R6830 刷入 Breed 与 Padavan 固件

发表评论
撰写评论