变量的赋值操作会执行两个动作, 首先编译器会在当前作用域中声明一个变量(如果之前没有声明过), 然后在运行时引擎会在作用域中查找该变量, 如果能够找到就会对它赋值。----《你所不知道的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. Huawei vlan 配置及vlan 间通讯

    Huawei Vlan配置及vlan 间通讯实例 组网需求:汇聚层交换机做为 PC 电脑的网关, PC3直连 SW2 属于 vlan 2,网关为 vlanif 2 接口地址192.168.2.1/24 ...

  2. 最短路径Dijkstra matlab

    Dijkstra: function [dist,pre, full_path]=MinRoad_Dijkstra(G,v0) n=0; if isfield(G,'w') && ~i ...

  3. 1.2环境的准备(二)之Pycharm的安装和使用

    目录: 1.Pycharm的安装 2.Pycharm的使用 (一)pycharm的安装: (1)官网下载:(分为两个版本,专业版和社区版,社区版就足够我们学习用的)https://www.jetbra ...

  4. 使用C#删除一个字符串数组中的空字符串

    C#中要如何才能删除一个字符串数组中的空字符串呢?随着微软对C#不断发展和更新,C#中对于数组操作的方式也变得越来越多样化.以往要实现过滤数组中的空字符串,都是需要实行循环的方式来排除和过滤.C#3. ...

  5. [LOJ 6030]「雅礼集训 2017 Day1」矩阵

    [LOJ 6030] 「雅礼集训 2017 Day1」矩阵 题意 给定一个 \(n\times n\) 的 01 矩阵, 每次操作可以将一行转置后赋值给某一列, 问最少几次操作能让矩阵全为 1. 无解 ...

  6. 分布式全局ID生成器设计

    项目是分布式的架构,需要设计一款分布式全局ID,参照了多种方案,博主最后基于snowflake的算法设计了一款自用ID生成器.具有以下优势: 保证分布式场景下生成的ID是全局唯一的 生成的全局ID整体 ...

  7. 在 CentOS/Fedora 下安装 JAVA 环境

    介绍 本文介绍如何在 CentOS 7(6/6.5). Fedora.RHEL 上安装 Java.Java是一个流行的软件平台,允许您运行Java应用程序. 本文涵盖了以下Java版本的安装: Ope ...

  8. tcp/ip 数据进入协议栈时的封装及分用过程图

  9. 20165318 2017-2018-2《Java程序设计》课程总结

    20165318 2017-2018-2<Java程序设计>课程总结 一.每周作业链接汇总 每周作业链接汇总 预备作业1:我期望的师生关系 预备作业2:C语言基础调查和java学习展望 预 ...

  10. jQuery 使用ajax,并刷新页面

    <script> function del_product_information(id) { $.ajax({ url: "{% url 'del_product_inform ...