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

    @Scheduled 注解 cron表达式 一个cron表达式有至少6个(也可能7个)有空格分隔的时间元素. 按顺序依次为 秒(0~59) 分钟(0~59) 小时(0~23) 天(月)(0~31,但是 ...

  2. .Net 初步学习笔记之三---变量

    一.存储变量的语法: 变量类型  变量名: 变量名=值: “=”号:把等号右边的值赋值给等号左边的变量 二.常用的变量 1.整数类型 int  //只存整数,不能存小数 2.小数类型 double   ...

  3. MVC 上传下载

    在Asp.net的WEBform中,上传文件与下载文件处理是很简单的事情,如果转为ASP.NET MVC呢?那就没有那么容易了,难少少,也不是很难,一起来看下本文吧.本文主要讲如何在Asp.net M ...

  4. 【转】Spring学习---Bean配置的三种方式(XML、注解、Java类)介绍与对比

    [原文]https://www.toutiao.com/i6594205115605844493/ Spring学习Bean配置的三种方式(XML.注解.Java类)介绍与对比 本文将详细介绍Spri ...

  5. October 12th 2017 Week 41st Thursday

    Be happy for this moment. This moment is your life. 为这一刻感到高兴,这一刻是你的人生. Yesterday Tencent became Asia ...

  6. Lombok 继承时应注意的点

    lombok项目的产生就是为了省去我们手动创建getter和setter等基本方法的麻烦,它能够在我们编译源码的时候自动帮我们生成getter和setter等方法.即它最终能够达到的效果是:在源码中没 ...

  7. [python] 私有变量和私有方法

    1.在Python中要想定义的方法或者变量只在类内部使用不被外部调用,可以在方法和变量前面加 两个 下划线 #-*- coding:utf-8 -*- class A(object): name = ...

  8. Android友盟推送

    当前版本号:v3.0.5 1.下载SDK解压并导入(import module,compile project(':PushSDK')),里面有demo,用demo的包名去官网添加一个应用,然后替换d ...

  9. 使用Tensorflow训练自己的数据

    训练自己的数据集(以bottle为例):   1.准备数据 文件夹结构: models ├── images ├── annotations │ ├── xmls │ └── trainval.txt ...

  10. JAVA 对基础知识的加强

    一.我们在写类的时候.设置字段的时候含义: 简单的javabean类: package jd.com.vo; import jd.com.mybaitstest.account; public cla ...