js let与块级作用域

221 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第4天,点击查看活动详情

作用域

在ES2015之前,ES中只有两种作用域,分别是全局作用域和函数作用域,在ES2015中新增了一个块级作用域

块指的是代码中对花括号所包裹起来的范围,例如if语句和for语句中的花括号都会产生块

if (true) {

}
for(let i = 0 ; i < 10 ; i ++ ){

}

let

以前块是没有独立的作用域,例如执行下面代码,还是能正常打印出来的,这对于复杂代码是非常不利和不安全的

if (true) {
    var foo='abc'
}
console.log(foo)//abc

有了块级作用于之后可以在代码中通过一个新的关键词let去声明变量,通过let声明的变量只能在所声明的代码块中被访问到

if (true) {
    let foo='abc' //块级作用域内定义的成员外部无法访问
}
console.log(foo)//报错

图片.png

for循环的计数器非常适合使用let,计数器只会在for循环体内有效,在循环体外引用就会报错

for(let i=0;i<10;+i++){}
console.log(i)//报错

典型的引用场景就是循环去注册事件,定义一个数组里面定义三个成员对象,然后去遍历整个数组去模拟为每一个成员对象添加onclick事件,在这个事件的处理函数中打印当前的计数器

循环结束过后调用这个数组任一成员的onclick事件发现打印的都是3,因为循环过后i累加到了3,无论打印哪个元素的click都是3

var ele=[{},{},{}]

for(let i=0;i<ele.length;+i++){
  ele[i].onclick=function(){
    console.log(i)
  }
}

ele[0].onclick() //3
ele[1].onclick() //3
ele[2].onclick() //3

在没有块级作用域之前可以借助闭包解决这个问题,闭包其实也是借助函数作用域去摆脱全局作用域的影响

var ele=[{},{},{}]

for(let i=0;i<ele.length;+i++){
    ele[i].onclick=(function(i){
        return function(){
            console.log(i);
        }
    })(i)
}

ele[0].onclick() //0
ele[1].onclick() //1
ele[2].onclick() //2

现在有了块级作用域就不用这么麻烦了,直接把声明计数器的var改为let就使i在块级作用域内被访问,这个问题就解决了,其实内部也是一种闭包的机制,因为在onclick执行的时候循环早就结束了,实际的i已经被销毁了,因为闭包的机制才可以拿到i

其实在for循环里有2层作用域,在for循环里使用i作为计数器,并在循环内部用let声明一个i,然后在控制台将这个i打印出来,缺发现控制台打印正常,所以这两个i互不影响,不在同一个作用域中

for(let i=0;i<3;+i++){
    let i = 'foo'
    console.log(i)
}

//打印
foo
foo
foo

除了块级作用域限制以外,let和var还有一个区别,let的声明不会出现提升的情况,var声明变量都会导致变量提升到最开始的位置,ES2015中从语法层面必须先声明再赋值,否则会报错

console.log(foo)//undefined,不会报错
var foo = 'foo'