词法作用域、块作用域(已完结)

本文详细探讨了JavaScript中的词法作用域和块作用域。词法作用域在预编译阶段由变量和块的书写位置决定,遵循逐层查找的原则,形成了嵌套的层次结构。块作用域包括with、try/catch、let和const的形式。let和const在ES6中引入,它们在块中创建作用域,let不进行变量提升,const则不可变。文章通过实例解释了遮蔽效应和作用域查找机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

词法作用域

什么是词法作用域

作用域共有两种主要的工作模型——词法作用域和动态作用域,JS采用词法作用域

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

由上面对词法作用域的描述可知,词法作用域的创建发生在预编译阶段,因为词法阶段属于预编译的一个过程

如果还是感觉不太理解,请考虑下面代码:

    function foo(a) {
        var b = a * 2;
        function bar(c) {
            console.log( a, b, c );
        }
        bar( b * 3 );
    }
    foo( 2 ); // 2, 4, 12

在这个例子中有三个逐层嵌套的词法作用域(在此我们称之为气泡),看下图:
嵌套的词法作用域

1包含着整个全局作用域,其中只有一个标识符:foo

2包含着foo 所创建的作用域,其中有三个标识符:a、bar 和b

3包含着bar 所创建的作用域,其中只有一个标识符:c

LHS与RHS的逐层查找便是逐层查找词法作用域,由内到外,由近到远。从外向内访问作用域是禁止的

无论函数在哪里被调用,也无论它如何被调用,它的词法作用域都只由函数被声明时所处的位置决定

    function foo(){
        var a = 200;
        bar();
    }
    function bar(){
        console.log(a);//100
    }
    var a = 100;
    foo();

虽然函数bar在foo中被调用,但是bar是定义在全局的,也就是说,bar的词法作用域嵌套关系为(由内到外):bar本身的词法作用域–>全局作用域。所以foo中的a并不影响输出结果

词法作用域怎么形成

你可以简单的认为词法作用域就是函数作用域(即上面图片中展示的相互嵌套的气泡)。但是这是片面的,因为,词法作用域由书写位置决定,那么,我在全局范围上声明一个变量,那么这个变量的词法作用域便是全局。

其实我想说的是不要简单的将词法作用域理解为函数作用域(即上图嵌套的气泡)。词法作用域是针对所有标识符而言的,根据书写位置,预编译阶段便会将对应的标识符放在对应的词法作用域中,然后就形成了词法作用域

所有由函数包裹的代码,都会在这个函数中形成词法作用域。就是上面图片展示的那样

    var a = 100;
    (function(){//会形成词法作用域,里面有a、b变量
        var a = 200;
        var b = 300;
        console.log(a);//200
    }());
    console.log(a);/*100,可见立即执行函数中给a赋值的操作并不影响全局a,这更说明了上述观点*/
    console.log(b);//访问不到立即执行函数中的b,此处报错
    function test(){
            var a = 100;
            setTimeout(function(){//该匿名函数同样形成词法作用域
                var a = 200;
                console.log(a);//200
            }, 2000);
            console.log(a);//100
    }
    test();
    //上述代码虽然存在闭包,但是这儿并没讨论闭包
词法作用域包含了什么

词法作用域中存放着代表变量或函数的标识符,以便进行函数与变量的LHS或RHS查询

查找标识符

在上一个代码片段中,引擎执行console.log(..) 声明,并查找a、b 和c 三个变量的引用。它首先从最内部的作用域,也就是bar(..) 函数的作用域气泡开始查找。引擎无法在这里找到a,因此会去上一级到所嵌套的foo(..) 的作用域中继续查找。在这里找到了a,因此引擎使用了这个引用。对b 来讲也是一样的。而对c 来说,引擎在bar(..) 中就找到了它

遮蔽效应

作用域查找会在找到第一个匹配的标识符时停止。在多层的嵌套作用域中可以定义同名的标识符,这叫作“遮蔽效应”(内部的标识符“遮蔽”了外部的标识符)。抛开遮蔽效应,作用域查找始终从运行时所处的最内部作用域开始,逐级向外或者说向上进行,直到遇见第一个匹配的标识符为止。

考虑下面代码:

    var a = 10;
    function test(){
        var a = 100;
        console.log(a);//遮蔽效应,输出100
    }
    test();

全局变量会自动成为全局对象(比如浏览器中的window 对象)的属性,因此可以不直接通过全局对象的词法名称,而是间接地通过对全局对象属性的引用来对其进行访问。window.a通过这种技术可以访问那些被同名变量所遮蔽的全局变量。

词法作用域查找只会查找一级标识符,比如a、b 和c。如果代码中引用了foo.bar.baz,词法作用域查找只会试图查找foo 标识符,找到这个变量后,对象属性访问规则会分别接管对bar 和baz 属性的访问(在‘LHS与RHS查询’中已经提到过)

块作用域

或许我们经常说——JS中没有块级作用域。这并没有错,但是如果更加深入的说,JS中是存在块级作用域的,只是并不明显或直接

with形式的块作用域

具体请看前面的文案——‘eval与with’

try/catch形式的块作用域

ES3 规范中规定try/catch 的catch 分句会创建一个块作用域。但是这儿有一个值的注意的地方,请看代码:

    try{
        var b = 100;
        undefined();
    }catch(error){
        var a = 200;
        function test(){
            console.log('name');
        }
    }
    console.log(b);//100
    console.log(a);//200
    test();//test
    console.log(error);//报错

catch不是生成了块级作用域吗?为什么a能在外部访问?

因为catch生成的块作用域仅对形参(这儿是error)有效,注意这点

let形式的块作用域

ES6引入了let关键字,提供了除var以外的另一种变量声明方式。

用let声明的变量会被绑定到let所在的块中(通常是{ .. } 内部)

    if(true){
        var aa = 100;
        let bb = 200;
    }
    console.log(aa);//100
    console.log(bb);//报错
    for(let i = 0; i < 3; i++){...}
    //这儿的i也会被绑定到后面的{}中

用let声明的变量在预编译阶段不会进行变量提升

    {
        console.log(a);
        let a = 100;    
    }
const形式的作用域

const——单词译为‘常量’

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 ); // 报错

ps:本文参考并引用下列书籍
《你不知道的JavaScript》(上卷)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值