每日读《你不知道的JavaScript(上)》| 面试高频之块级作用域

399 阅读4分钟

作用域的新大陆

全局作用域是人尽皆知的,前几章我们也着重介绍了函数作用域。

最后一个兄弟是 块级作用域

那么,什么是块级作用域呢?我们什么场景下会用到它呢?块级作用域有什么好处呢?...

让我们慢慢往下揭晓答案。

什么是块级作用域

原文中的描述是这样的:

变量的声明应该距离使用的地方越近越好,并最大限度地本地化。

块作用域是一个用来对之前的最小授权原则进行拓展的工具,将代码从在函数中隐藏信息拓展为在块中隐藏信息。

最直观地理解“块”,就是{}。

对,就是那个花括号。

比如:

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

在这个循环体里面,{} 就能形成个块。但注意啊,此时还不能真正地发挥一个块级作用域该有的功能。

因为我们定义循环体中的 i 时,用的是 var 这个关键字。

使用 var 声明的变量都属于外层作用域。

在上面这个 case 中,i 属于全局作用域。不信你看:

image.png

我把console.log(i)放到循环体外打印,还是能获取到 i 这个变量,说明 i 这个变量并没有被限制在 {} 的作用域里。

那么我们肯定希望,让这个 {} 成为“切切实实”的一个块级作用域,对吧。

此时我们只需要把 var 改成 let 即可。

image.png

看!这样在外面就访问不了变量 i 了。


这里还能引申出一个很重要的问题。var 和 let 的区别。

出现上面现象的原因是 var 定义一个变量的时候,会存在提升,但 let 不会。

啥是提升呢?

提升指的是,声明会被视为存在于其所出现的作用域的整个范围内。

var 声明的变量允许先使用再声明。

简单说来就是,var 定义的 i 会先提升到外层去,归属于外层作用域(这里体现在全局上)。

但 let 定义的 i 不会存在提升,必须先声明后使用,归属于当前作用域。

声明这个步骤的时候,就已经绑定到当前作用域不变了。

请往下看,注意理解绑定的含义。

怎么“制造”块级作用域

但为什么 let 就能够形成一个有效的块级作用域呢?

ES6引入了新的关键字 let。

let 关键字能够将变量绑定到所在的任何作用域中。

也就是说,用 let 声明的变量隐式地创建了它所在的块级作用域。

if (foo) { 
    { // <-- 显式的块
        let bar = foo * 2; 
        bar = something( bar ); 
        console.log( bar ); 
    } 
} 
console.log( bar ); // ReferenceError

如果去掉那个显示的 {} 块,就是相当于把 let 定义的 bar 隐式地附加在已经存在的 if 块级作用域中了。

说白了,就是用了 let 去定义后,能把变量绑定到当前的块级作用域中。

外部也就无法访问内部的变量了。

const 关键字也一样能够创建块级作用域变量。

它和 let 的唯一区别就是 const 定义的常量不能被修改。

块级作用域的用处和意义

还是拿上面的例子看问题。

for (var i = 0; i < 10; i++) {
  console.log(i);
}

这可能造成一个闭包,能发现不?

闭包是一个函数和其周围的状态(词法环境)的引用捆绑在一起。

闭包通常在嵌套函数中产生,当一个内层函数访问其外层函数的变量时,就会形成闭包。

好,我们改装一下啊:

var buttons = document.querySelectorAll('button');

for (var i = 0; i < buttons.length; i++) {
  buttons[i].addEventListener('click', function() {
    console.log('Button ' + i + ' clicked');
  });
}

当点击事件触发时,循环已经完成。

i 的值是最终的值,即buttons.length

那么我们在里面打印 i,是不是属于内层去访问外层作用域的变量了。

那是不是就形成了一个闭包。

而我们每次点击 button,都会让每一个事件处理程序共享同一个 i,导致打印出来的i都是同一个,这就是闭包带来的问题。

理解了这个之后,我们再来看,用 let 改造有什么好处。

根据上面的结果表明,改为 let 声明 i后,能够输出我们的预期结果,对吧。

因为当我们引入了 let 之后,每次循环迭代的时候 let 定义的变量都会创建一个新的绑定,而不是共享同一个绑定。

如此一来,每个点击事件处理函数都能捕获到自己的 i 值,就能够解决闭包带来的问题了。

小结

今天主要介绍了块级作用域的概念、用法以及优势,结合了前面的知识点,加深理解作用域的概念。

tomorrow 也继续加油!干巴爹~