阅读 564

是LHS查询还是RHS查询?编译器有话说

注:这段是编者在编写完正文之后的重要补充。如果不明白可看完正文返回看这个例子。
关于区别LHS和RHS,我突然想到之前看到的一个比喻:就好比你要给钱给某人,找不到那个人,你就把钱还给他兄弟了(创建一个还钱对象),毕竟不深究的话也是可以的,这就是LHS。但是如果你是追债,那必须要找到欠你钱的那个人,不可能叫别人替他还钱呀,这就是RHS,如果找不到那就只能报警,抛出异常。

在讲解两种查找类型之前,让我们先来简单看看编译原理(了解可跳过)

编译原理

QQ图片20210425111635.png

接下来我用 var a = 2; 这个程序来举例分析:

  1. 在分词过程会将由字符组成的字符串分解成词法单元,如:var、a、=、2、;
  2. 这个过程会将词法单元流转换成抽象语法树AST(一个由元素逐级嵌套所组成的代表了程序语法结构的树)上述大概就是这样的:

QQ图片20210425112930.png

  1. 代码生成会将AST转换为可执行的代码。简单来说就是有某种方法可以将var a=2;的AST转化为一组机器指令,用来创建一个叫a的变量(包括分配内存等),并将一个值存储在a中。

编译器在编译过程生成了代码,引擎执行它时,会通过查找变量a来判断它是否已经声明过。查找的类型就是我们将要讨论的两种:LHS查询和RHS查询。查找的过程需要作用域的协助,但是引擎执行查找的方式会影响最终的查找结果。

LHS 和 RHS

LHS(left-hand Side)引用和RHS(right-hand Side)引用,通常是指等号(赋值运算)的左右边的引用。
举例说明:

console.log(a);
复制代码

这里对a的引用是一个RHS引用,因为这里a并没有赋予任何值,由于我们只是想查找并取得a的值然后打印。

a = 2;
复制代码

这里对a的引用是一个LHS引用,我们并不关心当前的值是什么,只是想要为赋值操作找到目标。

通过这两个例子我们也能看出,LHS和RHS的含义是“赋值操作的左侧和右侧”并不一定意味这就是"="的左侧和右侧。赋值操作还有其他几种形式,因此在概念上最好将其理解为“赋值操作的目标是谁(LHS)”以及“谁是赋值操作的源头(RHS)”。



我再将小黄书上的那道例题解析一下,帮助大家理解

function foo(a) {
	var b = a;
	return a + b;
}

var c = foo(2);
复制代码

先告诉大家答案,这里一个有3个LHS查询和4个RHS查询。你们可以停顿一会儿,自己找找,也可以继续往下看。

LHS查找:

  1. c=... ,c是赋值操作的目标,所以对c需要LHS查询。
  2. 隐藏的a=2(隐式变量分配),在调用foo(2)的时候,需要将实参2赋值给形参阿,所以对a需要LHS查询
  3. b=.... ,同1

RHS查找:

  1. foo(2) ,foo(2)在赋值操作的右边,需要知道foo(2)的值,所以是RHS查询
  2. =a,a在赋值操作的右边,需要知道a的值,对a进行RHS查询
  3. a...,在return a+b中,需要知道a和b的值,应该对a和b分别进行RHS查询。

注:通过实例我们可以发现,如果查找的目的是对变量进行赋值,那么就会使用LHS查询;如果目的是获取变量的值,就会使用RHS查询

为什么要区分LHS 和 RHS

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

当对一个变量执行RHS查询时,如果遍历该变量所在处的词法作用域未能找到这个变量,JS引擎就会抛出 ReferenceError 错误。如果成功查询到了这个变量,但是对这个变量执行不合理操作,比如对一个非函数类型的值进行函数调用,或者引用null或undefined类型中的值中的属性,JS引擎就会抛出 TypeError 错误。

当对一个变量执行LHS查询时,同样在遍历作用域后无法找到该变量,在非ES5的严格模式下,系统就会自动在全局作用域中创建一个同名变量。而在ES5的严格模式下,LHS查询失败时JS引擎会抛出一个同RHS一样的 ReferenceError 错误。

文章分类
前端
文章标签