变量的作用域指的是:变量在什么范围内是可用的。
var 在 if 和 for 中 没有作用域,在 function 中有作用域,即var 没有块级作用域。块指的是代码块,即 if 和 for 后面的{ }
。
let 在 if 和 for 中有作用域,即let 有块级作用域。
没有块级作用域导致的问题
案例:点击第i个按钮,打印文字:“第i个按钮被点击”。
<button>按钮1</button>
<button>按钮2</button>
<button>按钮3</button>
<button>按钮4</button>
<button>按钮5</button>
如果我们这样写:
const btns = document.getElementsByTagName('button');
for (var i = 0; i < btns.length; i++) {
btns[i].addEventListener('click', function () {
console.log('第' + i + '个按钮被点击');
})
}
看似是 点击按钮1,就console “第1个按钮被点击” ;点击按钮2,就console “第2个按钮被点击”。
但实际上,不管点击哪个按钮,都会提示“第5个按钮被点击”。
为什么会这样?
首先,var没有块级作用域,var虽然定义在for里面,但其实定义的是一个全局变量。
for (var i = 0; i < btns.length; i++) { }
相当于是下面这样:
var i;
for (i = 0; i < btns.length; i++) { }
i都是全局变量。i的作用域是全局。
其次,for循环其实是这样循环的:循环几次,就有几个代码块
{
var i = 0;
btns[i].addEventListener('click', function () {
console.log('第' + i + '个按钮被点击');
})
}
{
var i = 1;
btns[i].addEventListener('click', function () {
console.log('第' + i + '个按钮被点击');
})
}
{
var i = 2;
btns[i].addEventListener('click', function () {
console.log('第' + i + '个按钮被点击');
})
}
{
var i = 3;
btns[i].addEventListener('click', function () {
console.log('第' + i + '个按钮被点击');
})
}
{
var i = 4;
btns[i].addEventListener('click', function () {
console.log('第' + i + '个按钮被点击');
})
}
这段代码里面,每个代码块有2个i:分别是btns[i]
和console.log('第' + i + '个按钮被点击')
。
btns[i]
在循环当时就被执行了,给每个按钮都添加了一个点击事件。但问题的核心在于:点击事件是在点击的时候,才被执行。并且点击的时候,i的值已经变成5了。
确切的说,是循环执行完的时候,i就已经变成5了。我们把代码换一种写法可以看得更直观一些:
var i=0; //i的初始值为0
{
btns[i].addEventListener('click', function () {
console.log('第' + i + '个按钮被点击');
})
}
//首次循环执行完,i++之后,i现在的值是1
{
btns[i].addEventListener('click', function () {
console.log('第' + i + '个按钮被点击');
})
}
//第二次循环执行完,i++之后,i现在的值是2
{
btns[i].addEventListener('click', function () {
console.log('第' + i + '个按钮被点击');
})
}
//第三次循环执行完,i++之后,i现在的值是3
{
btns[i].addEventListener('click', function () {
console.log('第' + i + '个按钮被点击');
})
}
//第四次循环执行完,i++之后,i现在的值是4
{
btns[i].addEventListener('click', function () {
console.log('第' + i + '个按钮被点击');
})
}
//for循环执行完毕,又执行了一下i++,i现在的值是5
//所以点击事件的时候,代码去找变量i,找到的值就是5
不管点击哪个按钮,都会显示 “第5个按钮被点击”。
解决方案和原因
方案1 使用闭包
为什么闭包可以解决问题: 因为var在函数里面有作用域。我们可以借助这个特性,让每次循环(每个function)都有自己的i
。
var btns = document.getElementsByTagName('button');
for (var i=0; i<btns.length; i++) {
(function (num) {
btns[i].addEventListener('click', function () {
console.log('第' + num + '个按钮被点击');
})
})(i)
}
这种写法相当于是:
var btns = document.getElementsByTagName('button');
for (var i=0; i<btns.length; i++) {
(function (num) { })(i) //num是形参,把0传给num
(function (num) { })(i) //把i=1传进去
(function (num) { })(i) //把i=2传进去
(function (num) { })(i) //把i=3传进去
(function (num) { })(i) //把i=4传进去
}
()()
写法解析:
第一个()
是一个函数整体;第二个()
表示立即执行(如果有参数则传递参数进去立即执行),也就是我们需要手动调用一下这个函数,要不函数不会被执行,也就不会给按钮添加上点击事件。
var 在 function 里面有作用域,function 可以在自己函数内部找到变量 i,也就不用到外面找全局的 i 了。
为了避免混淆,这里的形参使用了 num,一般写的时候会把形参和实参都写成i,不要弄混是一个注意点。
var btns = document.getElementsByTagName('button');
for (var i=0; i<btns.length; i++) {
(function (num) {
//var num = 0; 相当于是新建了一个变量num
btns[i].addEventListener('click', function () {
console.log('第' + num + '个按钮被点击');
})
})(i)
}
var btns = document.getElementsByTagName('button');
for (var i=0; i<btns.length; i++) {
(function (num) {
//var i = 0; 相当于是新建了一个变量i
btns[i].addEventListener('click', function () {
console.log('第' + i + '个按钮被点击');
})
})(i)
}
方案2 使用let替代var
for (let i = 0; i < btns.length; i++) {
btns[i].addEventListener('click', function () {
console.log('第' + i + '个按钮被点击');
})
}
使用let定义变量i的话,定义在代码块里面的i,只在代码块里面有效(代码块有自己的作用域了,i只属于当前这个大括号{}
),在代码块外面是读取不到的。
代码相当于是这样:
{ i = 0
btns[i].addEventListener('click', function () {
console.log('第' + i + '个按钮被点击');
})
}
{ i = 1
btns[i].addEventListener('click', function () {
console.log('第' + i + '个按钮被点击');
})
}
{ i = 2
btns[i].addEventListener('click', function () {
console.log('第' + i + '个按钮被点击');
})
}
{ i = 3
btns[i].addEventListener('click', function () {
console.log('第' + i + '个按钮被点击');
})
}
{ i = 4
btns[i].addEventListener('click', function () {
console.log('第' + i + '个按钮被点击');
})
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。