理解JS作用域笔记(一):五个阶段、词法作用域

开头

笔记视频内容源自B站JavaScript从入门到放弃 第九章 深入理解JS作用域,笔记为自行整理复习使用,欢迎一同学习交流,转载请通知原作者

一、内部原理

JS中的作用域分为两个作用域,一个是全局作用域,一个是函数作用域,JS引擎有着一套良好的规则去存储变量,并能够很方便的去找到变量,举一个例子:

<body>
    <script>
      var a = 2;
      console.log(a);
      function add() {
        var b = 3;
        console.log(a);
      }
      console.log(b);
    </script>
  </body>

a变量作为全局作用域,它能够在函数下被找到,这套规则就叫做作用域,b作为函数作用域下的变量无法在函数外被找到。理解好作用域也对后续理解this有做帮助。JS代码没有编译阶段,是一种边解释边执行的语言。

作用域内部可以划分为五个阶段(了解):

  1. 编译
  2. 执行
  3. 查询
  4. 嵌套
  5. 异常
1.1、编译阶段

我们以一个代码为例子:

<script>
    var a = 2;
</script>

我们将编译阶段划分成三个步骤:分词(分成不同的词法单元)、解析、代码生成

分词首先将语句分为一个个的词法单元:

var , a , = , 2, ;

在解释阶段内部将其放入一个数组或对象:

{
    "var":"keyword",//关键字
     "a":"indentifier",//标识符
     "=":"assignment",//分配
     "2":"interger",//整数
     ";":"eos",//(end of statement)结束语句
}

解析功能将对应的属性转化为一个抽象语法树(AST Abstract Snatax Tree)

image-20200706194645685

代码生成将抽象语法树转化为可执行的代码的过程,转化成一组机器指令,我们称这个过程为代码生成

1.2、执行阶段

代码生成后就来到了执行阶段,JS是边解释边执行,以一段代码举例子:

<script>
    var a = 2;
    console.log(a);
    console.log(b);
</script>

1、JS引擎执行时首先会查找作用域,首先查找a是否在当前作用域下,如果是,引擎就会直接使用这个变量。如果不在当前作用域则会继续往外找,直到找不到为止

2、如果找到了变量,就会将 2赋值给当前的这个变量,否则引擎就会抛出异常,就像代码示例中的b未定义,控制台抛出了异常

image-20200706195646513

1.3、查询阶段

先看代码:

<body>
    <script>
        var a = 2;
    </script>
</body>

查询阶段,引擎查询a变量,这样的查询叫做LHS(Left hand Side)左查询,RHS(Right hand Side)右查询用在函数的调用,比如:

<script>
    var a = 2;
    function add(){
    }
     add();
</script>

当变量出现在赋值操作的左侧时,查询为LHS,出现在右侧时如函数调用就为RHS

看一个例子:

function foo(a) {
    console.log(a);
    }
foo(2);

1、foo(2)对function函数对象做了一个RHS引用,这种引用是一种查询引用

2、函数传参a=2对a变量进行了LHS引用

3、console.log(a);对console对象进行RHS引用,并检查console中是否有log方法

4、console.log(a);对a进行RHS引用,并把得到的结果传给了console.log(a);

1.4、嵌套阶段

嵌套阶段是一个非常重要的阶段,嵌套机制是根据当前作用域进行查找的机制,先看代码:

<body>
    <script>
        //作用域变量的查找机制(重要)
        //在当前作用域下无法找到某个变量时,引擎就会在外层嵌套的作用域中继续查找,直到找到该变量或者是抵达最外层作用域(全局作用域)为止
      function foo(a) {
        console.log(a + b);
      }
      var b = 2;
      foo(4);
    </script>
  </body>

JS引擎在执行这句话时,经历了前三个阶段后,在当前的这个作用域下去寻找b,找不到的话会向外层(全局嵌套的作用域)寻找b变量。

image-20200706201249974

这里使用到了变量提升,js引擎会判断var变量,将其提升到最前面,所以不用去在意变量声明的位置

<body>
    <script>
      //作用域变量的查找机制(重要)
      function foo(a) {
        console.log(a + b);
        function fo() {
          console.log(a + b);
        }
      }
      var b = 2;
      foo(4);
    </script>
  </body>

这个代码同样的思路,无论嵌套多深,寻找作用域都是从内而外去寻找,直到全局作用域下没发现位置,报错

1.5、异常阶段

异常就是在浏览器上出现的一些错误

例子1:

<body>
    <script>
        function fn(a){
            a = b;
        }
        fn(2);
    </script>
</body>

image-20200706202243744

我们能发现b is not defined,因为b是一个未声明的变量,在当前作用域的RHS查询时未找到,在全局作用域也找不到,所以输出了错误

例子2:

function fn2(){
    var b = 0;
    b();
}

image-20200706202220159

例子3:

function fn(){
   a = 1;
}
fn();
console.log(a);

如果fn()这一行被注释掉就会引起报错:a is not defined,加上这一行则不会报错,这是因为执行函数时,引擎执行了LHS查询,将1赋值给了a变量,如果在该函数中没有声明a这个变量,引擎将会在全局作用域中声明一个a变量并进行赋值操作,所以外层作用域下的log方法能够访问到a这个变量

但是在严格模式下就无法进行这种操作:

function fn(){
   'use strict';
    a = 1;
}
fn();
console.log(a);

image-20200706202822838

所以一般在使用函数时都会加上这句话,防止一些非法操作

二、词法作用域

2.1、作用域查找机制

作用域分为两种,一个是词法作用域,一个是动态作用域,JS中不存在所谓的动态作用域,所以我们通过一个案例来看看词法作用域

<body>
    <script>
      function foo(a) {
        var b = a * 2;
        function bar(c) {
          console.log(a, b, c);
        }
        bar(b * 3);
      }
      foo(2);
    </script>
  </body>

image-20200706204047251

首先外层有一个作用域,foo函数有一个函数作用域,bar也有一个作用域,有三层作用域。从作用域的角度分析,我们将代码分为三层作用域,最外层全局作用域1,foo函数作用域2,bar函数作用域3,1包含2包含3

image-20200706204234986

首先1全局作用域下找到了一个foo(词),在2的作用域下找到了b、a、bar(三个词),3下只有一个c(词)

image-20200706204418110

执行foo时,2赋值给a,a变量是属于foo作用域的,foo作用域内有找到a就赋值给它,bar函数把b*3的值赋值给c,来到bar的作用域,发现a并不在当前bar函数的作用域下,于是去它的外层作用域(foo)查找,找到了a,再去找b,b不在当前作用域于是往外层(foo)寻找。最后再去找c,c在当前作用域下(bar)就赋值。也就是说作用域是由代码里函数声明时决定的,你在哪里声明,他所在的执行上下文就被决定了,通过词法作用域才可以预测代码在执行过程中如何查找标识符。

2.2、遮蔽效应

什么是遮蔽,作用域查找从运行时所处的最内部作用域开始,逐级向上进行,直到遇到第一个匹配的标识符为止

在多层的嵌套作用域中可以定义同名字的标识符,这叫做遮蔽效应

<body>
    <script>
      var a = 0;
      function test() {
        var a = 1;
        console.log(a);
      }
      test();
    </script>
  </body>

在进入test函数作用域时首先查找a变量是否在当前作用域,发现第一个匹配的就不会再去向外匹配查询,这样内层作用域就覆盖了外层的变量,这就是遮蔽效应

所以全局声明的变量要小心函数中再去声明相同的变量,以免遮蔽效应的产生。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值