编译的三个过程、作用域、LHS和RHS查询

作用域是什么

初次阅读的一些感悟

这一本书主要是我在空闲时间段所读的,初次阅读发现本书风格确实有趣,一般的技术类书籍如《JavaScript高级程序设计》读起来都相对枯燥,而这本书却是给了我一个较为轻松的氛围,阅读体验很好,阅读下来收获挺多。

这一系列文章主要是记录我对书中的一些知识点的理解,以书中的结构以及讲解深入了解JavaScript的特性,更好的使用它,所以语言风格以自己更好理解的角度出发、较为随意,虽然我的其他笔记博客等风格也不是很严谨🧐,但争取读到本系列文章的同学也能理解🚀,里面有书中的原话也有我自己的理解,若是哪个地方写的有误希望能够指出来🤝。


编译

在了解作用域之前,我们先来了解在传统的编译语言中,程序源码在执行之前会经历的三个步骤,即编译

⛱️编译的三个步骤

  • 分词/词法分析

    这个过程会将由字符组成的字符串分解为有意义的代码块,这些代码块称之为词法单元

    var a = 2;为例,我们来看词法单元。

    这段代码会被分解为vara=2;这五个词法单元,而代码中的空格是否会被 当作词法单元取决于这个空格是否有意义,在这个例子中空格是没有意义的,所以并没有被分解为词法单元。若是在var str = 'a b'中,这个空格就会被分解为此法单元,因为这个空格有意义,是字符串的一部分。

    分词与词法分析的区别:

    分词和词法分析之间的区别是非常微妙、晦涩的,主要差异在于词法单元的识别是通过有状态还是无状态的方式进行的。简单来说,如果词法单元生成器在判断a是一个独立的语法单元还是其他语法单元的一部分时,调用的是有状态的解析规则,那么这个过程就被称为词法分析。

    分词就是把一段字符串信息分解为一个个有单独意义的单词,通常以空格为界限进行分词。而词法分析则是在分词的基础上将这些单词进行更加深入的分析,分析他们在这段上下文中他们的属性、意义, 对这些单词符号进行识别、分类和标记,从而确定其类型以及在编译中的作用 。

  • 解析/语法分析

    这个过程是将词法单元流(数组)转换成为一个由元素逐级嵌套所组成的代表了程序语法结构的树。这个树被称为“抽象语法树(AST)”。

    var a = 2;

在这里插入图片描述

  • 代码生成

    将AST转换为可执行代码的过程称为代码生成。这个过程与语言、目标平台等信息有关。简单来说,就是通过一种方法将var a = 2;转换为一组机器指令,用来创建一个叫做a的变量(包括分配内存等),并将一个值存储在a中。


对于JavaScript来说,它的编译过程不是发生在构建之前的,JavaScript引擎不会有大量的时间(与其他语言编译器相比)用来进行优化。大部分情况下,JavaScript的编译发生在代码执行前的几微秒(甚至更短)的时间内,然后立即执行。

理解作用域

🎫在执行var a = 2;的过程中,出现的三个重要角色:引擎、编译器、作用域。

  • 引擎: 从头到尾负责整个JavaScript程序的编译以及执行过程。
  • 编译器: 负责语法分析以及代码生成的工作。
  • 作用域: 负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常 严格的规则,确定当前执行的代码对这些标识符的访问权限。

var a = 2;变量的赋值操作会执行两个动作,首先是编译器会在当前作用域中声明一个变量a(之前没有声明过),然后在运行时引擎会在作用域中查找变量,如果能够找到该变量则进行赋值。


🧿引擎进行的查找变量过程

引擎在进行查找变量的过程中会进行LHS和RHS查询。

  • LHS查询是指对变量进行赋值操作的左侧查询。当一个变量出现在赋值操作的左边时,就会进行LHS查询,目的是为了找到变量的容器(存储地址),以便对变量进行赋值操作。

  • RHS查询是指对变量进行赋值操作的右侧查询。当一个变量出现在不是赋值操作的右边时,就会进行RHS查询,目的是为了找到变量的值。

var a = 2;
a = 3;

在第一行代码中,声明了变量a,为其分配了空间(存储地址),并进行了赋值。

而在第二行代码中,发生了一次LHS查询,目的是为了把变量a的值改为2,所以是为了找到这个赋值的空间进行更改值操作,即LHS=>值是谁

var a = 1;
//1被当做参数传递给函数时发生了一次LHS查询,因为console是一个对象,log是它的一个方法,a是形参,而之前a = 1,所以现在1被赋值给了a
console.log(a);

在在第一行代码中,声明了变量a,为其分配了空间(存储地址),并进行了赋值。

在第二行代码中,发生了一次RHS查询,目的是为了打印a的值,即RHS=>给谁值

💎LHS和RHS的含义是“赋值操作的左侧或右侧”并不一定意味着就是“=赋值操作符的左侧或右侧”。赋值操作还有其他几种形式,因此在概念上最好理解为“赋值操作的目标是谁(LHS)”以及“谁是赋值操作的源头(RHS)”。


📔作用域

作用域(Scope)指的是程序中定义变量的区域。通俗点说,作用域就是变量的有效范围,即该变量在程序中可以被访问的区域或代码段。JavaScript中有全局作用域和局部作用域两种类型

全局作用域可以理解为整个JavaScript程序的作用域,程序中无论在哪个位置定义变量,只要没有使用关键字限制作用域,那么该变量就是全局变量,它可以被程序中任何地方的代码所访问。

局部作用域指的是函数作用域,即函数内部定义的变量只能在该函数内部访问,外部代码无法访问函数内部定义的变量。

在JavaScript中,作用域采用的是词法作用域(Lexical Scope),也就是静态作用域,即调用函数时采用的作用域到底是哪个作用域,在函数定义的时候就已经确定了,与函数调用的位置无关。

作用域链(Scope Chain)指的是在当前执行环境中可访问的所有变量对象的集合。JavaScript引擎在查找变量时,会按照作用域链向上查找,直到全局作用域,或者找到该变量为止。作用域链的顶端是当前执行环境的变量对象(Variable Object),底端是全局执行环境的变量对象。

在JavaScript中,每个函数都会在创建时生成一个作用域链,该链包含了所有父级作用域。当代码执行到一个函数时,会创建该函数的执行上下文,并将该执行上下文的变量对象添加到作用域链的顶端,最后得到的作用域链就是由当前执行环境的变量对象和所有父级执行环境的变量对象依次组成的链式结构。

var a = 1;  // 全局作用域
function foo() {
  var b = 2;  // 局部作用域
  console.log(a);  // 访问全局作用域中的变量 a
  console.log(b);  // 访问局部作用域中的变量 b
}
foo();

📓区分LHS和RHS的重要性

因为在变量还没有声明时(任何作用域中都无法找到)的情况下,这两种查询的行为是不一样的。

function foo(a) {
    console.log(a + b);
    b = a;
}

foo(2);

在这段代码中,第一次对b进行RHS查询时无法找到该变量,b是一个未声明的变量,所有作用域中都无法找到。在这种情况下,引擎就会抛出ReferenceError异常。

而在非严格模式下引擎执行LHS查询时,出现这种情况就会在全局作用域中创建一个具有该名称的变量,并将其返回给引擎。在严格模式中,LHS查询失败时,并不会去创建一个全局变量,而是抛出抛出ReferenceError异常。

而当RHS查询到了一个变量,但却尝试对这个变量进行不合理操作,如试图对一个非函数类型的值进行函数调用或者引用null和undefined类型中的值中的属性就会抛出TypeError错误。

另外,理解二者的区别对于我们改bug,调试代码很有作用。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值