你不知道的JavaScript(上卷)--块作用域

410 阅读4分钟

先看一段示例代码

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

变量i的块作用域(如果存在的话)将使得其只能在for循环内部使用,如果在函数中其他地方使用会导致错误。块级作用域对保证变量不会被混乱地服用及提升代码的可维护性都有很大的帮助。

1.with

用 with 从对象中创建出的作用域仅在 with 声明中而非外 部作用域中有效。

具体请点击下边链接,在我的另一篇文章里有具体的解释。

juejin.cn/post/686266…

2.try/catch

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

代码示例:

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

尽管这个行为已经被标准化,并且被大部分的标准 JavaScript 环境(除了老 版本的 IE 浏览器)所支持,但是当同一个作用域中的两个或多个 catch 分句 用同样的标识符名称声明错误变量时,很多静态检查工具还是会发出警告。实际上这并不是重复定义,因为所有变量都被安全地限制在块作用域内部,但是静态检查工具还是会很烦人地发出警告。

为了避免这个不必要的警告,很多开发者会将 catch 的参数命名为 err1、 err2 等。也有开发者干脆关闭了静态检查工具对重复变量名的检查。

块级作用域 ES6与ES6之前的转化

ES6考虑下面的代码:

{     
    let a = 2;     
    console.log( a ); // 2 
} 
 
console.log( a ); // ReferenceError

这段代码在 ES6 环境中可以正常工作。但是在 ES6 之前的环境中如何才能实现这个效果? 答案是使用 catch。

try{throw 2;}catch(a){     
	console.log( a ); // 2 
} 
 
console.log( a ); // ReferenceError

3.let

let 关键字可以将变量绑定到所在的任意作用域中(通常是 { .. } 内部)。换句话说,let 为其声明的变量隐式地了所在的块作用域。

为块作用域显式地创建块可以部分解决这个问题,使变量的附属关系变得更加清晰。通常来讲,显式的代码优于饮食或一些精巧但不清晰的代码。

let隐式声明代码示例

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

let显式声明代码示例

var foo = true;

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

只要声明是有效的,在声明中的任意位置都可以使用 { .. } 括号来为 let 创建一个用于绑定的块。

let循环

一个 let 可以发挥优势的典型例子就是之前讨论的 for 循环。

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

for 循环头部的 let 不仅将 i 绑定到了 for 循环的块中,事实上它将其重新绑定到了循环的每一个迭代中,确保使用上一个循环迭代结束时的值重新进行赋值。

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

let作用域(let声明)

let (a = 2) {     
	console.log( a ); // 2 
} 
 
console.log( a ); // ReferenceError

同隐式地劫持一个已经存在的作用域不同,let 声明会创建一个显示的作用域并与其进行 绑定。显式作用域不仅更加突出,在代码重构时也表现得更加健壮。在语法上,通过强制 性地将所有变量声明提升到块的顶部来产生更简洁的代码。这样更容易判断变量是否属于某个作用域。

这种模式同很多人在函数作用域中手动将 var 声明提升到函数顶部的方式很接近。let 声 明有意将变量声明放在块的顶部,如果你并没有到处使用 let 定义,那么你的块作用域就 很容易辨识和维护。

但是这里有一个小问题,let 声明并不包含在 ES6 中。官方的 Traceur 编译器也不接受这 种形式的代码。

我们有两个选择,使用合法的 ES6 语法并且在代码规范性上做一些妥协。

/*let*/ { 
            let a = 2;     
            console.log( a ); 
        } 

        console.log( a ); // ReferenceError

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!