乐趣区

关于javascript:JS中的块作用域和遮蔽效应

JS 中的代码块是什么?

咱们的程序是由一条条语句形成的,语句是依照自上而下的程序一条条执行的。代码块 (block) 也被称为复合语句(coumpound statement),其作用是通过“{}”将多条语句组合在一起。

{
    // 复合语句
    var a = 10;
    console.log(a);
}

那么为什么要将多条语句组合在一起呢?

因为咱们能够将多条语句“打包”在一起,放在一个 JS 只期待繁多语句的中央。这句话看上去有些艰涩,然而配合代码了解很容易:

在上面代码中,条件判断语句 if (true) 前面,能够只写一条语句,不写 {} 也能够失常运行。

if (true) console.log('hello'); // 只写一行语句,程序也能够失常运行

// 输入 hello

然而如果咱们想在条件判断后做更多事件,就须要用到{},在这个 JS 只期待繁多语句的中央放上更多语句:

if (true) {console.log('hello');
    console.log('world');
    console.log('bye'); 
}

块级作用域是什么?

块级作用域 (block scope) 指的是大括号中的所有变量和函数只能够在这个大括号蕴含的区域中拜访到,在括号外是拜访不到的。上代码:

{
    var a = 10; // var 处于全局作用域
    let b = 20;
    const c = 30; // let 和 const 都处于代码块作用域
    console.log(a); 
    console.log(b); 
    console.log(c); 
}
console.log(a); 
console.log(b); 
console.log(c); 

将下面的代码放到调试工具中,能够看到 var 和 let、const 申明的变量属于不同的作用域。当代码在大括号中执行时,程序能够拜访到 2 个作用域(代码块和全局)中的变量,如下图:

然而当代码执行到大括号之外,就会报错,因为当代码块中的语句执行完后,代码块作用域就会隐没(从栈中弹出),因而只留下一个全局作用域,其中只绑定了一个变量 a,无法访问到变量 b 和 c:

因而,该程序最终在控制台的输入后果是:

因为在块级作用域外打印 b 出错,程序会在此处进行运行,不过可想而知,打印 c 也会呈现同样的谬误。


遮蔽效应是什么?

不同作用域中雷同名称的变量就会触发遮蔽效应(shadowing)。

var 的遮蔽效应

看代码,猜后果:

var a = 100;
{
    var a = 10;
    let b = 20;
    const c = 30;
    console.log(a);
    console.log(b);
    console.log(c);
}

控制台打印如下:

在大括号中的变量 a 会使得第 1 行申明的变量 a 有效,这就叫遮蔽效应。因为这两处 a 指向的都是全局作用域中的 a。

那么,如果在括号外再打印一下 a 呢?

var a = 100;
{
    var a = 10;
    let b = 20;
    const c = 30;
    console.log(a);
    console.log(b);
    console.log(c);
}
console.log(a); // 猜后果

让咱们来到调试工具中退出断点:

当程序执行完第 1 行时,这时全局变量 a 的值为 100:

当程序执行完第 3 行时,全局变量 a 的值被二次申明的 var a = 10 从新赋值为 10,这两行语句中的 var a 指向的都是同一个全局变量 a:

因而,控制台打印如下:

let 的遮蔽效应

看代码,猜后果:

let b = 100;
{
    var a = 10;
    let b = 20;
    const c = 30;
    console.log(a);
    console.log(b);
    console.log(c);
}
console.log(b);

控制台打印如下:

在大括号中的变量 b 同样会使第 1 行申明的变量 b 有效,造成变量遮蔽。然而与 var 不同的是,let 领有块级作用域,因而最初一行的 b 打印进去的仍是第 1 行的申明的 b。

让咱们再次进入调试工具中加上断点:

能够分明的看到,程序中共存在 3 个作用域,1 个是全局作用域,剩下 2 个是块级作用域(代码块和脚本)。只管第 1 行的 let 申明处于全局,然而 let 关键词会本人发明一个块级作用域(图中的脚本),处于内存中独立的空间,贮存了值为 100 的变量 b。在大括号中的变量 b,处于内存中另一个独立的空间,贮存了另一个值为 20 的变量 b。

因而,程序第 7 行的 b 拜访的是代码块中的变量 b,第 10 行拜访的是脚本(也能够了解成另一个代码块)中的变量 b。

const 的遮蔽效应

const 的遮蔽效应和 let 统一,将之前的代码稍作批改:

const c = 100;
{
    var a = 10;
    let b = 20;
    const c = 30;
    console.log(a);
    console.log(b);
    console.log(c);
}
console.log(c);

控制台打印如下:

函数中的遮蔽效应

遮蔽效应不仅产生在块级作用域中,也产生在函数中。看代码:

const c = 100;
function x () {
    const c = 30;
    console.log(c);
}
x();
console.log(c);

控制台打印如下:


非法遮蔽是什么?

如果应用 var 关键字触发遮蔽效应,是齐全可行的,如下:

var a = 100;
{
    var a = 20;
    console.log(a);  // 20
}

同理,应用 let 关键字触发遮蔽效应也是无效的:

let a = 100;
{
     let a = 20;
    console.log(a);  // 20
}

然而如果用 var 去遮蔽 let,在程序运行之前就会报错:

let a = 100;
{
    var a = 20;
    console.log(a);  // 语法错误
}

那么,如果用 let 去遮蔽 var 是否也会报错呢?看代码:

var a = 100;
{
    let a = 20;
    console.log(a); 
}

控制台打印后果如下:

程序并不会报错。那么为什么用 var 去遮蔽 let 就会报错呢?

因为遮蔽效应的准则是:反复申明的语句不能够超出本人所处的作用域。也就是,当咱们应用 var 在块级作用域中从新申明变量 a 时,因为 var 只有全局作用域或者函数作用域,因而块级作用域无奈限度 var 的申明,这时 var a 就会超出本人所在的作用域,而 let 关键字又不容许在同一个作用域中反复申明,因而程序报错。

因而,只须要将下面代码中的块级作用域改成函数作用域,程序就不会报错了:

var a = 100;
function x () {
    var a = 20;
    console.log(a); 
}
x(); // 20
console.log(a); // 100

用 const 触发遮蔽效应和 let 的状况同理,因而就不赘述了。

const b = 100;
{
    const b = 20;
    console.log(b); // 20
}

块级作用域和词法作用域

词法作用域就是定义在词法阶段的作用域。换句话说,词法作用域是由你在写代码时将变量和块作用域写在哪里来决定的。

词法作用域的变量查找规定是:如果在以后作用域中找不到该变量,就会去上一层作用域中进行查找,以此类推。块级作用域也享有同样的查找规定:

const a = 20;
{
    const a = 100;
    {
        const a = 200;
        console.log(a); // 200
    }
}
const a = 20;
{
    const a = 100;
    {const a = 200;}
}
console.log(a); // 20
const a = 20;
{
    const a = 100;
    {console.log(a); // 100 往上一级作用域进行查找
    }
}

以上就是 JavaScript 中块级作用域和遮蔽效应的常识。

退出移动版