1

JS 编译之 LHS RHS

一、前言

最近和朋友聊技术的时候,聊到 LHS RHS,我竟然没听说过 没听说过。。。 于是成功引起了我的好奇心。 关于两个专业术语的讨论起自对《你不知道的JavaScript》一书的阅读学习。

二、编译简述

尽管通常将JavaScript归类为“动态”或“解释执行”语言,但事实上它是一门编译语言。这个事实对你来说可能显而易见,也可能你闻所未闻,取决于你接触过多少编程语言,具有多少经验。但与传统的编译语言不同,它不是提前编译的,编译结果也不能在分布式系统中进行移植。

尽管如此,JavaScript引擎进行编译的步骤和传统的编译语言非常相似,在某些环节可能比预想的要复杂。

在传统编译语言的流程中,程序中的一段源代码在执行之前会经历三个步骤,统称为“编译”。

  • 分词/词法分析(Tokenizing/Lexing)
  • 解析/语法分析(Parsing)
  • 代码生成

比起那些编译过程只有三个步骤的语言的编译器,JavaScript引擎要复杂得多。例如,在语法分析和代码生成阶段有特定的步骤来对运行性能进行优化,包括对冗余元素进行优化等。

JavaScript引擎,编译器概述

  • 从头到尾负责整个JavaScript程序的编译及执行过程。
  • 引擎的好朋友之一,负责语法分析及代码生成等脏活累活。

摘录来自: “你不知道的JavaScript(上卷)”

以上内容看起来有些高深,但是与下面内容并无多少关联,只是背景,有兴趣的可以深入研究一下。
对于下面的内容,我们只需要知道 JS代码的执行需要JS引擎的,而JS引擎的在执行代码前会先对其进行编译(有些地方称之为预解析),引擎最终执行的是经过编译之后的代码
LHS RHS 这两个术语就是出现在引擎对变量进行查询的时候,接下来看看在具体例子中 站在编译器和引擎的角度看看它们是怎么思考工作的,以及这个过程中如何触发了LHS RHS.

三、具体代码的编译执行过程中 如何触发 LHS RHS

以书中的赋值语句为示例

var a = 2;

事实上编译器会进行如下处理。

  1. 遇到var a,编译器会询问作用域是否已经有一个该名称的变量存在于同一个作用域的集合中。如果是,编译器会忽略该声明,继续进行编译;否则它会要求作用域在当前作用域的集合中声明一个新的变量,并命名为a。
  2. 接下来编译器会为引擎生成运行时所需的代码,这些代码被用来处理a = 2这个赋值操作。引擎运行时会首先询问作用域,在当前的作用域集合中是否存在一个叫作a的变量。如果是,引擎就会使用这个变量;如果不是,引擎会继续查找该变量。

摘录来自: 你不知道的JavaScript。

以上为书中描述,但是个人觉得可以补充一些描述如下

  1. 编译器在编译一段js代码时,相当于对这段代码进行预解析,为了引擎更好的进行二次解析。在预解析时,先构建好代码的上下文环境,建立作用域链,在每个作用域中进行变量声明提升和更特别的函数声明提升。变量的声明提升会在当前作用域的集合中声明一个新的变量,如a。另外,在声明每一个新的变量之前,编译器会询问作用域是否已经有一个该名称的变量存在于同一个作用域的集合中, 如有则报错。

       console.log(a)
       var a = 2;

    的预解析之后,其实相当于

    var a
    console.log(a) // undefined
    a = 2;
  2. 接下来编译器会为引擎生成运行时所需的代码,这些代码被用来处理a = 2这个赋值操作。引擎运行时会首先询问作用域,在当前的作用域集合中是否存在一个叫作a的变量。如果是,引擎就会使用这个变量;如果不是,引擎会沿作用域链继续查找该变量;如果查到根作用域也没有查到,会自动声明a。

ps: 关于声明提升,作用域链,如有疑惑,后续章节解释。

上述过程中,第二步中编译器生成了代码,引擎执行它时(第3行 a = 2),会通过查找变量a来判断它是否已声明过。查找的过程由作用域进行协助,但是引擎执行怎样的查找,会影响最终的查找结果。
此时引擎会就在对变量a进行LHS查询,另外一个查询的类型叫RHS。

四、LHS RHS 概念定义

我打赌你一定能猜到“L”和“R”的含义,它们分别代表左侧和右侧。
什么东西的左侧和右侧?是一个赋值操作的左侧和右侧。

换句话说,当变量出现在赋值操作的左侧时进行LHS查询,出现在右侧时进行RHS查询。

讲得更准确一点,RHS查询与简单地查找某个变量的值别无二致,而LHS查询则是试图找到变量的容器本身,从而可以对其赋值。从这个角度说,RHS并不是真正意义上的“赋值操作的右侧”,更准确地说是“非左侧”。

你可以将RHS理解成retrieve his source value(取到它的源值),这意味着“得到某某的值”。

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

摘录来自: 你不知道的JavaScript。

以上为书中解释,以赋值操作符为标志,加上一些特殊情况的理解。但是我觉得可以有其他的理解方式。

五、LHS RHS 个人理解

讲得更准确一点,RHS查询与简单地查找某个变量的值别无二致,而LHS查询则是试图找到变量的容器本身,从而可以对其赋值。

举个例子来理解就是:RHS是找到你在哪个座位,看看你的样子拍个照;LHS同样是找到你在哪个座位,但是一眼不看直接踹飞 换个人坐这儿。
简单来讲有两个区别:1.关不关心你现在的状态 2.是否改变你的状态(不管是改变一部分,还是完全改变)。

所以书中这句原话

是引擎执行怎样的查找,会影响最终的查找结果。

我觉得可以反过来理解,正是因为查找结果的不同分出了两种查找类型。

左侧右侧的分类理解更多是以 代码的形态为标准分类的。个人觉得可以按查找目的为标准分类,RHS是为了读取变量的值,LHS是为了改变变量的值。

六、总结

LHS RHS就是对变量查询的两种查询类型,区别在于查询的结果,或者说查询的目的,在代码上直观体现为变量位置形态的不同。其中涉及的编译器引擎工作过程,对于更深入的理解掌握js很有帮助。 其中个人理解有不合适的地方还请指正。


baozw
411 声望5 粉丝