虽然,函数作用域是最常见的作用域单元,当然也是现行JavaScript中最普遍的设计方法,但是同样也有其它方式的作用域单元。
如下代码,通常我们希望变量i只在for循环的上下文中使用,而忽略了实际上变量i会被绑定在外部作用域中(函数或全局)的事实:
for (var i = 0; i < 10; i ++) {
// do sth
}
console.log(i); // 10
// 再来看另一段代码:
var flag = true;
if (flag) {
var value = flag * 2;
}
console.log(value); // 2
变量value仅在if声明的上下文中使用,因此如果能将变量value局限在if声明,块内将是一个很有意义的事情,但是,很可惜,它仍旧会被绑定到外部作用域中(函数或全域)的事实。
在Java语言中,类似在for循环中定义一个变量i,然后在一对花括号中使用,结束后,变量i就被系统回收了,再之后如果使用变量i,就会报错!
这就是一个块作用域!!!!!!!!!
那为什么在JavaScript中,没有这个?
因为,我们使用了var声明变量,因此,无论在哪里声明变量,它们最终都会绑定到外部作用域当中。
上面的for或if所包裹出来的代码,只是为了风格更易读而伪装出的形式上的块作用域,因此使用这种形式的代码,要确保没有在作用域其它地方使用,只能依靠自觉性。
Good Lucy! ES6拯救了我们,引入了新的关键字,使得我们可以既能声明变量,也能使用块作用域:
- let => 块作用域变量
- const => 块作用域常量
通常,我们只需要在{...}中,使用以上两个关键字,它们就能绑定在最近的{}块作用域中,结束后就会被自动回收。
var flag = true;
if (flag) {
{ // 显示的块
let value = flag * 2;
console.log(value); // 2
}
console.log(value); // ReferenceError
}
function process(data) {
// 在这里做点有趣的事情
}
var someReallyBigData = { .. };
process(someReallyBigData);
var btn = document.getElementById("my_button");
btn.addEventListener("click", function click(evt) {
console.log("button clicked");
}, /*capturingPhase=*/false);
click函数的点击回调并不需要someReallyBigData变量。理论上这意味着当 process(..) 执行后,在内存中占用大量空间的数据结构就可以被垃圾回收了。但是,由于click函数形成了一个覆盖整个作用域的闭包,JavaScript引擎极有可能依然保存着这个结构(取决于具体实现)。
/**
* 有了let后,我们可以改进如下:
*/
function process(data) {
// 在这里做点有趣的事情
}
{ // ok,显示声明块作用域, 执行完块后就可以回收了
let someReallyBigData = { .. };
process(someReallyBigData);
}
var btn = document.getElementById("my_button");
btn.addEventListener("click", function click(evt) {
console.log("button clicked");
}, /*capturingPhase=*/false);
再来看看for循环这个例子
for (let i = 0; i < 10; i ++) {
console.log(i);
}
/**
* for循环头部的let不仅将i绑定到了for循环
* 的块中,事实上它将重新绑定到了循环的每一
* 个迭代中,确保使用上一个循环迭代结束时的
* 值重新进行赋值。
* 下面通过另一种方式来说明每次迭代时进行的
* 重新绑定的行为:
*/
{
let i;
for (i = 0; i < 10; i ++) {
let temp = i; // 每个迭代重新绑定
console.log(temp);
}
}