4

JavaScript 作用域的延伸 — 闭包

 3 years ago
source link: http://misaka.im/index.php/archives/35/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

JavaScript 作用域的延伸 — 闭包

2021.02.06默认分类 0 评

其实在《有用但不愿意使用的机制 — 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)
}
JavaScript

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

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

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

使用 IIFE

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

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

曲线救国

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

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

对例子的反思

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

使用另外一个例子来说明

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

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK