三、3.4块作用域

尽管函数作用域是最常见的作用域单元,除JS外的很多编程语言都支持块作用域。
块作用域是一个用来对之前的最小授权原则进行扩展的工具,将代码从在函数中隐藏信息扩展为在块中隐藏信息

你可能连一行带有块作用域风格的代码都没有写过但是你肯定熟悉下面的JS代码:

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

在for循环的头部定义了变量 i,通常是因为只想在for 循环内部的上下文中使用i ,而忽略了 i会被绑定在外部作用域(函数或全局)中。
变量的声明应该距离使用的地方越近越好,并最大限度的本地化。另一个例子:

var foo=true;
if(foo){
    var bar=foo*2;
    bar=something(bar);
    console.log(bar);
}

bar 变量仅在if声明的上下文中使用。这段代码是为了风格易读而伪装出的形式上的块作用域,如果使用这种形式,要确保没在作用域其他地方使用bar只能靠自觉。

3.4.1 with

with关键字是块作用域的一种形式,
用with从对象中创建出的作用域仅在with声明中而非外部作用域中有效。

3.4.2 try/catch

JS的ES3规范中规定 try/catch的catch分句会创建一个块作用域,其中声明的变量仅在catch内部有效,例如:

try{
    undefined();//执行一个非法操作来强制制造一个异常
}
catch(err){
    console.log(err);//能够正常执行
}
console.log(err);//ReferenceError:err not found

如上所示,err仅在catch分句内部,当视图从别处引用它时,会抛出错误。

尽管这个行为已经被标准化,并且被大部分的标准JS环境(除了老版本的IE浏览器)所支持,但当同一个作用域中的两个或多个catch分句用同样的标识符名称声明错误变量时,很多静态检查工具还是会发出警告。实际上这并不是重复定义,因为所有变量窦娥比安全地限制在块作用域内部,但是静态检查工具还是会发出警告。
为了避免这个不必要的警告,很多开发者会将catch的参数命名为err1、err2等。也有开发者干脆关闭了静态检查工具对重复变量名的检查。

3.4.3 let

ES6引入了新的 let 关键字,提供了除var 以外的另一种变量声明方式。
let 关键字可以将变量绑定到所在的任意作用域中(通常时{..}内部)。用let将变量附加在一个已经存在的块作用域上的行为是隐式的。

var foo=true;
if(foo){
    let bar=foo*2;
    bar=something(bar);
    console.log(bar);
}
console.log(bar);//ReferenceError

但是隐式的创建块作用域,在开发和修改代码的过程中,如果没有密切关注哪些块作用域中有绑定的变量,并且习惯性的移动这些块或者将其包含在其他的块中,就会导致代码变得混乱。

为块作用域显式的创建块可以分解这个问题。使变量的附属关系变得更加清晰。

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

上面代码片段中,用 if声明内部显式的创建了一个块,如果需要对其进行重构,整个块都可以被方便的移动而不对外部 if 声明的位置和语义产生任何影响。
只有声明是有效的,在声明中的任意位置都可以使用{..}来为 let 创建一个用域绑定的块。
在第四章,讨论提升,提升是指生命会被是为存在于其所作用域的整个范围内。
但是 let 进行的声明不会在块作用域中进行提升。声明代码运行之前,声明并不“存在”。

1.垃圾收集

另一个块作用域非常有用的原因和比好以及回收内存垃圾机制相关。
此处简要说明,而内部实现原理,也就是闭包机制会在第五章详细解释

function process(data){
    //
}
var someReallyBigData={..};
progress(someReallyBigData);

var btn=document.getElementById("my_button");
btn.addEventListener("click",function click(evt){
    console.log("button clicked");
},/*capturingPhase*/false);

click函数点击回调并不需要someReallyBigData变量。理论上意味着当process(..)执行后,在内存中占用大量空间的数据结构就可以被垃圾回收了。
但是,由于click函数形成了覆盖整个作用域的闭包,JS引擎可能依然保留这个结构。

块作用域可以打消这种顾虑,可以让引擎清楚的知道没有必要继续保持someReallyBigData。
为变量显示声明块作用域,并对变量进行本地绑定。

function process(data){
}
//在这个块中定义的内容可以销毁了
{
let someReallyBigData={..};
progress(someReallyBigData);
}
var btn=document.getElementById("my_button");
btn.addEventListener("click",function click(evt){
    console.log("button clicked");
},/*capturingPhase=*/false);

2.let 循环

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

for循环头部的let不仅将 i 绑定到了for循环的块中,事实上它将其重新绑定到了循环的每一个迭代中,确保使用上一个循环迭代结束时的值重新开始进行赋值。
下面通过另一种方式来说明每次迭代时重新绑定的行为:

{
let j;
for(j=0;j<10;j++){
    let i=j;//每个迭代重新绑定
    console.log(i);
    }
}

迭代重新绑定的原因,子啊第五章讨论闭包说明。

由于let 声明附属于一个新的作用域而不是当前的函数作用域(也不是全局作用域),当代码中存在对于函数作用域中var 声明的隐式依赖时,就会有很多隐藏的陷阱,如果用 let 来替代var则需要在代码重构的过程中付出额外的精力。
考虑以下代码:

var foo=true,baz=10;
if(foo){
    var bar=3;
    if(baz>bar){
        console.log(baz);
    }
    //...
}

这段代码可以简单的被重构成下面的同等形式:

var foo=true,baz=10;
if(foo){
    var bar=3;
    //...
}
if(baz>bar){
    console.log(baz);
}

但是,在使用块级作用域的变量时需要注意以下变化:

var foo=true,baz=10;
if(foo){
    let bar=3;
    if(baz>bar){ //<--移动代码时不要忘记了bar
    console.log(baz);
    }
}

3.4.4 const

除了 let 秒ES6还引入了 const ,同样可以擦混关键块作用域变量,但其值是固定的(常量)。以后任何试图修改值的操作都会引起错误。

var foo=true;
if(foo){
    var a=2;
    const b=3;//包含在if中的块作用域常量

    a=3;//正常
    b=4;//错误
}
console.log(a);//3
console.log(b);//ReferenceError!
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值