变量的赋值操作会执行两个动作, 首先编译器会在当前作用域中声明一个变量(如果之前没有声明过), 然后在运行时引擎会在作用域中查找该变量, 如果能够找到就会对它赋值。----《你所不知道的JavaScript(上)》 P7

而要讲的 LHS 和 RHS 就是上面说的对变量的两种查找操作,查找的过程是由作用域(词法作用域)进行协助,但是引擎执行怎样的查找, 会影响最终的查找结果。

1、LHS(Left Hand Side)和 RHS(Right Hand Side)

当变量出现在赋值操作的左侧时进行 LHS 查询, 出现在右侧时进行 RHS 查询。讲得更准确一点, RHS 查询与简单地查找某个变量的值别无二致, 而 LHS 查询则是试图找到变量的容器本身, 从而可以对其赋值。 从这个角度说, RHS 并不是真正意义上的“赋值操作的右侧”, 更准确地说是“非左侧”。  ----《你所不知道的JavaScript(上)》 P7

  简单来说,

  (1)LHS查询指的是找到变量的容器本身,从而可以对其进行赋值。也就是找到赋值操作的目标。LHS查询的时候会沿着作用域链进行查询,找到的话就会将值赋值给这个变量,如果到达作用域顶端仍然找不到,就会在作用域链顶端创建这个变量(在严格模式中 LHS 查询失败时, 并不会创建并返回一个全局变量, 引擎会抛出同 RHS 查询失败时类似的 ReferenceError 异常 

var a = 2;

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

  (2)RHS查询就是普通的查询变量的值,即获取变量的值。RHS查询的时候会沿着作用域链进行查询,找到的话就会取得这个值并返回,如果到达作用域顶端仍然找不到,引擎就会抛出 ReferenceError异常如果 RHS 查询找到了一个变量, 但是你尝试对这个变量的值进行不合理的操作,比如试图对一个非函数类型的值进行函数调用, 或着引用 null 或 undefined 类型的值中属性, 那么引擎会抛出另外一种类型的异常, 叫作 TypeError。
(注:ReferenceError 同作用域判别失败相关, 而 TypeError 则代表作用域判别成功了, 但是对结果的操作是非法或不合理的。)

  举个栗子:

console.log(a);

这里的a就是一个RHS引用,因为console.log需要获取到a的值才能输出a的值。当然这里的console.log也是一个RHS引用,这里对console 对象进行RHS 查询,并且检查得到的值中是否有一个叫作log 的方法。例子中的a因为没有声明过,所以会抛出错误。如下图所示:

2、实例详解

实例1:

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

(1)foo(..) 函数的调用需要对 foo 进行RHS引用 ,意思是“去找到 foo 的值, 并把它给我 ”。

(2)这里还有一个容易被忽略却非常重要的细节。代码中隐式的 a=2 操作可能很容易被你忽略掉。这个操作发生在 2 被当作参数传递给foo(..) 函数时,2 会被分配给参数a。为了给参数a(隐式地)分配值,需要进行一次LHS 查询。

(3)console.log(a)这里还有对a进行的RHS引用,并且将得到的值传给了console.log(..)。console.log(..) 本身也需要一个引用才能执行, 因此会对console对象进行RHS查询,并且检查得到的值中是否有一个叫作log的方法。

所以这里一共进行了1次LHS查询3次RHS查询。

让我们把上面这段代码的处理过程想象成一段对话, 这段对话可能是下面这样的。

  引擎: 我说作用域, 我需要为 foo 进行 RHS 引用。 你见过它吗?

  作用域: 别说, 我还真见过, 编译器那小子刚刚声明了它。 它是一个函数, 给你。

  引擎: 哥们太够意思了! 好吧, 我来执行一下 foo。

  引擎: 作用域, 还有个事儿。 我需要为 a 进行 LHS 引用, 这个你见过吗?

  作用域: 这个也见过, 编译器最近把它声名为 foo 的一个形式参数了, 拿去吧。

  引擎: 大恩不言谢, 你总是这么棒。 现在我要把 2 赋值给 a。

  引擎: 哥们, 不好意思又来打扰你。 我要为 console 进行 RHS 引用, 你见过它吗?

  作用域: 咱俩谁跟谁啊, 再说我就是干这个。 这个我也有, console 是个内置对象。给你。

  引擎: 么么哒。 我得看看这里面是不是有 log(..)。 太好了, 找到了, 是一个函数。

  引擎: 哥们, 能帮我再找一下对 a 的 RHS 引用吗? 虽然我记得它, 但想再确认一次。

  作用域: 放心吧, 这个变量没有变动过, 拿走, 不谢。

  引擎: 真棒。 我来把 a 的值, 也就是 2, 传递进 log(..)。

  ----《你所不知道的JavaScript(上)》 P9

实例2:

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

以上代码中有3个LHS与4个RHS,分析如下:

(1)var c中的c需要被赋值,在赋值操作的左侧,所以对c进行LHS引用。

(2)变量c需要被赋值,他的值是foo(2),那么foo(2)的值是多少呢,需要查找foo(2)的值,在赋值操作的右侧,所以对foo(2)进行了一次RHS查询。

(3)隐含赋值操作,将2传递给function foo(a){……}函数的参数a,a=2,a在赋值操作的左侧,对a进行了一次LHS查询。

(4)var b=a;中,b需要被赋值,处在赋值操作的左侧,所以对b进行了一次LHS查询,b的值将从a来,那么右侧的a的值从何而来呢?这就需要对赋值操作右侧的a进行了一次RHS查询。

(5)return a+b;中,需要找到a与b的值的来源,a与b都在赋值操作的右侧,才能得到a+b的值,所以对a与b都是进行一次RHS查询。

注:console.log(..) 本身也需要一个引用才能执行,因此会对console 对象进行RHS 查询,并且检查得到的值中是否有一个叫作log 的方法。这里不会再对log进行RHS查询。因为对console查询完毕后,对象属性访问规则会接管对log属性的访问。也就是说,如果是访问对象的属性就不存在LHS查询和RHS查询了,找不到就返回undefined。

3、小结:

如果查找的目的是对变量进行赋值, 那么就会使用 LHS 查询; 如果目的是获取变量的值, 就会使用 RHS 查询。赋值操作符会导致 LHS 查询。 =操作符或调用函数时传入参数的操作都会导致关联作用域的赋值操作。JavaScript 引擎首先会在代码执行前对其进行编译, 在这个过程中, 像 var a = 2 这样的声明会被分解成两个独立的步骤:

1. 首先, var a 在其作用域中声明新变量。 这会在最开始的阶段, 也就是代码执行前进行。

2. 接下来, a = 2 会查询(LHS 查询) 变量 a 并对其进行赋值。

LHS 和 RHS 查询都会在当前执行作用域中开始, 如果有需要(也就是说它们没有找到所需的标识符), 就会向上级作用域继续查找目标标识符, 这样每次上升一级作用域, 最后抵达全局作用域, 无论找到或没找到都将停止。不成功的 RHS 引用会导致抛出 ReferenceError 异常。 不成功的 LHS 引用会导致自动隐式地创建一个全局变量(非严格模式下), 该变量使用 LHS 引用的目标作为标识符, 或者抛出 ReferenceError 异常(严格模式下)。

 

LHS 和 RHS----你所不知道的JavaScript系列(1)的更多相关文章

  1. js值----你所不知道的JavaScript系列(6)

    1.数组 在 JavaScript 中,数组可以容纳任何类型的值,可以是字符串.数字.对象(object),甚至是其他数组(多维数组就是通过这种方式来实现的) .----<你所不知道的JavaS ...

  2. js类型----你所不知道的JavaScript系列(5)

    ECMAScirpt 变量有两种不同的数据类型:基本类型,引用类型.也有其他的叫法,比如原始类型和对象类型等. 1.内置类型 JavaScript 有七种内置类型: • 空值(null) • 未定义( ...

  3. 闭包----你所不知道的JavaScript系列(4)

    一.闭包是什么? · 闭包就是可以使得函数外部的对象能够获取函数内部的信息. · 闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分. · 闭包就 ...

  4. 提升----你所不知道的JavaScript系列(3)

    很多编程语言在执行的时候都是自上而下执行,但实际上这种想法在JavaScript中并不完全正确, 有一种特殊情况会导致这个假设是错误的.来看看下面的代码, a = 2; var a; console. ...

  5. let和const----你所不知道的JavaScript系列(2)

    let 众所周知,在ES6之前,声明变量的关键字就只有var.var 声明变量要么是全局的,要么是函数级的,而无法是块级的. var a=1; console.log(a); console.log( ...

  6. 你所不知道的JavaScript数组

    相信每一个 javascript 学习者,都会去了解 JS 的各种基本数据类型,数组就是数据的组合,这是一个很基本也十分简单的概念,他的内容没多少,学好它也不是件难事情.但是本文着重要介绍的并不是我们 ...

  7. 你所不知道的javascript数组特性

    工作中,我们经常使用js的数组,但是,下面的东西你见过吗? 1,文本下标: var a=[]; a[-1]=1; 你想过数组的下标为负数的情况吗?我们对数组的下标规定从0开始.但是上面那么写也还是可以 ...

  8. JavaScript中你所不知道的Object(二)--Function篇

    上一篇(JavaScript中你所不知道的Object(一))说到,Object对象有大量的内部属性,而其中多数和外部属性的操作有关.最后留了个悬念,就是Boolean.Date.Number.Str ...

  9. 一些你所不知道的VS Code插件

    摘要: 你所不知道的系列. 原文:提高 JavaScript 开发效率的高级 VSCode 扩展之二! 作者:前端小智 Fundebug经授权转载,版权归原作者所有. 作为一名业余爱好者.专业人员,甚 ...

随机推荐

  1. 为什么Sql Server的查询有时候第一次执行很慢,第二次,第三次执行就变快了

    老外提问: Hi, I have an sql query which takes 8 seconds in the first run. The next run there after takes ...

  2. USB AUDIO Device CLASS Requests

    写在前面 本文翻译自 USB Device Class Definition for Audio Devices 1998年版.主要是鄙人个人使用,所以只挑对我有用的翻译.有些我认为不是很重要的可能就 ...

  3. android的hwc浅析【转】

    https://blog.csdn.net/alien75/article/details/39290109 注:本文档基于kk进行分析,着重于概念的精确定义和版本历史演变 一.关于hwc的介绍 广义 ...

  4. 几种模型文件(CDM、LDM、PDM、OOM、BPM)

    概念数据模型 (CDM): 帮助你分析信息系统的概念结构,识别主要实体.实体的属性及实体之间的联系.概念数据模型(CDM)比逻辑数据模型 (LDM)和物理数据模型(PDM)抽象.CDM 表现数据库的全 ...

  5. 团队作业——Alpha冲刺 4/12

    团队作业--Alpha冲刺 冲刺任务安排 杨光海天 今日任务:着手进行编辑界面的布局,插入控件,并进行参数调整. 明日任务:继续完善编辑界面控件,学习控件交互功能. 郭剑南 今日任务:上网查阅学习了关 ...

  6. console.time方法与console.timeEnd方法

    在Node.js中,当需要统计一段代码的执行时间时,可以使用console.time方法与console.timeEnd方法,其中console.time方法用于标记开始时间,console.time ...

  7. HTML标签之marquee

    版权声明:本文为博主原创文章.未经博主同意不得转载. https://blog.csdn.net/zkn_CS_DN_2013/article/details/25229719 <html> ...

  8. mod_php和mod_fastcgi和php-fpm的介绍,对比和性能数据

    1.php中fastcgi和php-fpm是什么东西 最近在研究和学习php的性能方面的知识,看到了factcgi以及php-fpm,发现我对他们是少之又少的理解,可以说几乎是一无所知,想想还是蛮可怕 ...

  9. 关于ip判断

    php正则实现ip段判断 $ip = '22.18.10.2'; $arrayip = array('123.12.*.*','22.18.10.*');//ip段 $ipregexp = implo ...

  10. php 基于redis计数器类

    本文引自网络 Redis是一个开源的使用ANSI C语言编写.支持网络.可基于内存亦可持久化的日志型.Key-Value数据库,并提供多种语言的API. 本文将使用其incr(自增),get(获取), ...