1

变量的作用域指的是:变量在什么范围内是可用的。

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 + '个按钮被点击');
    })
}

白话前端
109 声望8 粉丝