JavaScript中的LHS和RHS

LHS和RHS之前我们先来回忆一下最简单的赋值操作!

var test=100;
console.log(test);

以上代码的意思简单我们理解为把右边赋值给左边test变量,然后输出打印结果。

可是我们要是深入理解你就会发现在这个过程当中,还发生了一些其他的事情

而这些事情就是今天我们要说的LHS和RHS

就比如之前那段代码:

var test=100;

JS会将其看成两句声明:var testtest=100 弄成两个部分: 编译代码执行

var test 这个部分也就是定义声明的时候就开始进行编译(编译器)

test=100 而后面这一段赋值声明会留在原地等待代码执行(引擎)

那么JS中的变量赋值操作会被拆分执行为两个动作:

  1. JS编译器会在当前作用域中声明这个变量,当然是这个变量不存在的情况下!

  2. 然后在运行代码时,JS引擎会在作用域查找这个变量,如果能找到就会对它进行一些操作,例如:赋值操作

而以上这两部的操作又间接引出LHS和RHS的概念!

所以这里的JS编译又与传统的编译语言是不同的!

LHS与RHS基本概念

LHS 全称为: Left-hand Side(左侧引用)

LHS其实就是赋值操作左侧查询,LHS查询试图找到变量的容器本身,从而对其赋值!

注意: =操作符调用函数时传入参数的操作都会导致赋值操作!

小结

通常情况下,如果查找的目的是对变量进行赋值,那么就会使用LHS查询 也就是当变量出现在赋值操作左侧

RHS 全称为: Right-hand Side(右侧引用)

RHS其实就是赋值操作右侧查询,可以理解为需要获取到某值

小结

通常情况下,如果查找的目的是获取变量或函数的值,就会使用RHS查询, 也就是变量出现在赋值操作右侧

总的来说LHS和RHS通常是指等号赋值运算的时候,左右边的引用!

可能这样说对于理解LHS和RHS还是比较抽象,我们要用一个案例来解释一下!

举个梨子

console.log(test);

按照LHS与RHS的查找规范, 这段代码就是一个所谓的RHS右侧引用

因为这里test变量,我们并没有对其进行赋值操作,而只是想在作用域当中查找这个变量并取得值,然后输出打印,所以执行的就是RHS

test = 100;

而这段代码当中 按照LHS与RHS的查找规范, 就是一个LHS左侧引用

因为此时JS并不关心当前的值是什么, 只是想要给当前这个赋值操作找到一个目标容器!

我们再看一个案例, 大家可以猜猜看以下代码当中有多少个LHS查询 又有多少RHS查询?

代码如下

function foo(num) {
console.log(num);
}foo(100);

分析

  1. 首先function foo(num)这里就存在一个LHS查询 因为100赋值给了num形参对吧,也就相当于num=100,那么就会在这个函数作用域当中先找出num这个变量容器!

  2. foo(100) 本身就是在求返回值的操作, 在作用域当中就会进行RHS查找这个foo(100)函数是否存在, 并没有对其进行赋值操作, 所以这里很明显就是一个RHS查找

  3. 最后console.log(num)这里其实又是一个RHS查询, 因为在这行代码中,我们只有一个变量 num 被使用,因此在 console.log(num) 中只有一个RHS查询,在作用域中查找, 用于获取num的值!

所以正确答案是以上代码当中存在1个LHS查询, 2个RHS查询

所以通过上面的案例最后我们可以把LHS与RHS简单的理解为以下概念:

赋值操作的目标是谁,那么就是LHS(左侧引用查找),

谁是赋值操作的源头,则就是RHS(右侧引用查找)

面试题: 请找出以下代码当中,所有的LHS和所有的RHS

function test(num) {
var num2 = num;
return num + num2;
}
var num = test(100);

代码分析

包含LHS的代码

  1. var num = test(100) 这一段代码很显然是一个LHS,因为num在赋值运算的左边,也就是赋值操作的目标,所以要对num变量进行LHS查询, 那么这里的查询过程就是由作用域(词法作用域)进行配合查找的!

  2. function test(num)的形参num在调用test(100)时,将实参100赋值给了形参num,也就是num=100,因为形参num在赋值运算的左边,也就是赋值操作的目标,所以要对形参num进行LHS查询

  3. var num2 = num 这一段代码也是一个LHS,因为num2在赋值运算的左边,也就是赋值操作的目标,所以要对num2进行LHS查询

包含RHS的代码

  1. var num = test(100)这段代码虽然有LHS查询但同时也有RHS 原因之前其实我们也说过,可以把这段代码分成两个部分来看,其中就有test(100)调用这部分,而这里恰恰是要求得一个结果,需要知道test(100)的值是多少 那么根据谁是赋值操作的源头是谁则就是RHS,从而获取test(100)的返回值

  2. var num2 = num也跟上面同理虽然有LHS查询但同时也有RHS,也就是其中也有num这个变量在赋值运算符右边, 此时需要知道num的值 那么根据谁是赋值操作的源头是谁则就是RHS

  3. return num + num2 这里我们按照(词法作用域查找)思维来说需要知道 numnum2的值, 也就是说只是想查找这两个变量,并取得它们的值,然后才是进行求和操作, 所以这里就是RHS查找, 也就是需要分别对num 和num2都进行RHS查询

    那么根据上面的案例当中,要看出左侧右侧并不一定意味这就是=号的左侧和右侧,赋值操作还有其他几种形式

小结

如果查找的目的是对变量进行赋值,那么就会使用LHS查询
如果目的是获取变量的值,就会使用RHS查询

LHS与RHS查找规则

从之前的案例当中,不管是LHS还是RHS 都会在当前执行的作用域中开始查找变量

LHS在查找的时候,是把右边赋值给左边变量那么就会对左边变量进行当前作用域中的LHS查询,来判断是否声明过!

RHS在查找的时候,是先看谁是赋值操作的源头, 然后在这个基础之上进行当前作用域中的RHS查询,简单点说也就是在当前作用域中查找右边变量或者函数表达式来判断是否已经声明过!

那么LHS和RHS在当前作用域当中如果没有找到所需的标识符 就会根据作用域链向上一级作用域继续查找该标识符,以此类推这样每次上升一层作用域去查找, 最后到达全局作用域就会停止,这也是我们之前讲过的作用域链!

我们可以回顾一下之前的作用域链

如图

LHSRHS都会在当前执行代码的所在环境进行查找, 如果没有找到,才往上一层查找 以此类推, 一旦抵达顶层全局作用域之后,可能找到了你所需的变量,也可能没找到,但无论如何查找过程都将停止!

结合(作用域+编译器+JS引擎)来理解LHS和RHS

我们用一段代码来说明:

var test = 100;

分析

JS引擎 会认为这里有两个完全不同的声明

一个由编译器在编译时处理也就是var test

另一个则由JS引擎在运行时处理,也就是test = 100

编译器遇到var test 会向当前作用域询问是否已经存在这个变量, 如果有就交给编译器继续进行编译赋值, 否则它会要求作用域当前作用域的中声明一个新的变量test

然后JS引擎运行代码的时候,会首先询问作用域,在当前的作用域中是否存在一个叫作test变量, 如果有,JS引擎就会使用这个变量, 如果没有JS引擎会继续根据作用域链查找该变量

如果JS引擎最终找到了test变量,就会将100赋值给它, 否则JS引擎就会抛出一个异常!

LHS与RHS异常问题

RHS查询当前作用域中如果找不到变量,引擎会抛出ReferenceError错误

例如: 直接在整个作用域当中打印输出一个没有定义声明变量或函数就会报ReferenceError错误

如图

例如:

代码如下

function foo(num) {    console.log(num + data);     data = num;}foo( 100);

以上的代码当中进行了RHS但无法查找到, 因为作用域是往上查找的,这里也很明显是找不到的!

如图

函数也是一样, 在调用一个完全没有声明函数时也会抛出ReferenceError错误

就比如说如下代码:

test();

如图

所以在作用域中直接调用一个没有定义的test()函数 直接在整个作用域进行了RHS查找也没有查到

自然会报ReferenceError错误

但是如果RHS作用域中查找到变量,但是进行了不规范的操作, 比如: 在末尾加了个()简单点说就是试图对一个非函数类型值/变量进行函数调用 那么JS引擎会抛出另一种异常叫做 TypeError(类型异常)

如图

还有就是如果RHS作用域中查找到变量, 但是引用了nullundefined 类型的值中的属性,也会报一个TypeError(类型异常)的错误!

知识点复习

在 JS 中,null 和 undefined 是特殊的值,用于表示变量或属性不存在或没有值。如果你引用 null 或 undefined 类型的值中的属性,意味着你尝试访问一个不存在的属性或者一个没有被赋值的变量。具体而言,如果你尝试在一个 null 或 undefined 类型的值中访问一个属性,JS引擎会抛出一个类型错误(TypeError),提示你不能在 null 或 undefined 上访问属性。

例如,以下代码会抛出TypeError(类型异常)!

var obj=null;obj.username;

如图

所以这里总结以下:

ReferenceError作用域查找失败,也就是说找不到这个变量或者函数才抛出来的

TypeError则代表作用域查找成功了, 但是对结果的操作是非法不合理的!

JS引擎执行LHS查询时,如果在所有作用域中找不到目标变量,就会在全局作用域中创建一个与该变量名相同的全局变量,并将其返回给JS引擎,前提是在非严格模式下 这也正好呼应了我们前面所讲解的隐式全局变量的真正含义!

代码分析以下函数当中的a = 100其实是一个LHS,但是变量a并没有在函数块当中进行声明就直接赋值了,那么没声明的变量正常情况下,作用域中是找不到的那么LHS则会在"全局作用域"中创建一个与该"变量"名相同的"全局变量a"

如图

我们再看一段代码

function foo(a){  num = a;  //num = 100}foo(100);

分析

上面的代码执行的LHS查询,在非严格模式下,JS引擎直到在全局作用域中都没有找到num这个变量,所以它就在全局作用域中声明了一个变量num 当然也对变量a进行一次RHS查询以获得变量a的值, 所以此时结果不会报错, 并且num也被赋值为100

我们也可以使用Chrome调试工具当中的Sources断点来查看全局作用域当中是否真的存在这个num变量,果不其然,我们在global中找到了这个num变量

如图

但是如果是严格模式下会ReferenceError的错误

代码如下:

"use strict";function test(){    a=100;}test();console.log(a);

如图

也就是说在严格模式下,LHS查询是无法帮助我们建立隐式全局变量

LHS与RHS的区别

其实我们在熟悉了LHS和RHS抛出的异常问题之后,就会明白它们彼此的区别在什么地方了!

RHS查询在所有嵌套作用域中找不到所需的变量或函数,引擎就会抛出ReferenceError异常

但是要注意的是,如RHS查询找到了一个变量,但是对这个变量的值进行不合理的操作, 例如: 使用null或者undefnied类型的属性,这种违规操作, JS引擎会抛出TypeError异常

LHS查询 相比之下,非严格模式的情况下 执行LHS查询时,如果在顶层作用域也无法找到目标变量,那么全局作用域会创建一个具有该名称的隐式全局变量,并将其返回给JS引擎, 当然如果是在严格模式下,LHS查询找不到目标变量时, 依旧会抛出ReferenceError异常

总结

所以区分LHS和RHS很重要的依据就是最终 查询在作用域链中找不到需要的变量函数 会抛出什么!

LHS和RHS都会在当前执行作用域中开始查询,当前没找到,就会根据作用域链上级作用域继续查找目标标识符

不成功的RHS会导致抛出ReferenceError异常

不成功的LHS自动隐式全局作用域中创建一个同名的全局变量 严格模式下也会抛出ReferenceError异常

作用域与LHS和RHS之间的关系

之前我们学过作用域JS引擎用来管理如何在当前作用域以及嵌套的子作用域中根据标识符名称进行变量查找的一套规则

如果查找的目的是对变量进行赋值,那么就会使用LHS查询

如果目的是获取变量的值,就会使用RHS查询

小提示:

要注意一点: 如果代码中引用了类似于foo.bar.baz,那么词法作用域查找只会试图查找foo标识符,找到这个变量后,再根据对象属性访问规则会分别接管, 并对barbaz属性的访问!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/43666.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言 printf函数缓冲机制

printf不立即打印到stdout的原因 printf函数使用了缓冲机制。当我们调用printf时,输出通常不会立即显示在屏幕上,而是先存储在一个缓冲区中。这是为了提高I/O操作的效率。 缓存数据输出的原理 stdio库维护了一个缓冲区。当缓冲区满了,或者在特定条件下,缓冲区的内容会被刷新…

前端如何让网页页面完美适配不同大小和分辨率屏幕

推荐使用postcss插件,它会自动将项目所有的px单位统一转换为vw等单位(包括npm安装的第三方组件),从而实现适配,具体配置规则可参考官网或npm网站介绍。 另外对于大屏的适配,需要缩放网页,可使用…

欧姆龙安全PLC及周边产品要点指南

电气安全、自动化设备作业安全,向来是非常非常之重要的!越来越多的客户在规划新产线、改造既有产线的过程中,明确要求设计方和施工方将安全考虑进整体方案中进行考虑和报价!作为一名自动化电气工程师,尤其是高级工程师…

养宠经验分享猫咪经常掉毛怎么办?最值得买的宠物空气净化器分享

身为资深铲屎官,深知若偷懒不打扫,家中便成猫毛纷飞、异味缭绕的战场,尤其换季时,更是雪上加霜。长期处于这样的环境,不仅我们头疼眼涩、咳嗽气喘,对老人、小孩、孕妇等敏感群体更是健康大敌。 幸运的是&a…

Vagrant配合VirtualBox搭建虚拟机

目录 前言一、软件下载及安装1.下载2.安装扩展: 二、创建一个虚拟机1.Vagrant官方镜像仓库 三、使用远程工具连接虚拟机1.修改相关配置文件 四、虚拟机克隆及使用1.通用配置2.简单搭建一个java环境3.克隆虚拟机1.重命名虚拟机(可选)2.打包指定…

靶场练习 手把手教你通关DC系列 DC1

DC1靶场通关教程 文章目录 DC1靶场通关教程前言一、信息收集1.主机存活2.端口收集3.网页信息收集4.目录收集4.1 Nikto4.2 Dirb 信息收集总结 二、漏洞发现与利用1. 发现2. 利用 三、FlagFlag1Flag2Flag3Flag4Flag5(提权) 前言 本次使用的kali机的IP地址为192.168.243.131 DC1的…

机器学习 - 比较检验

列联表 列联表(Contingency Table)是一种用于显示两个或多个分类变量之间关系的表格。它广泛应用于统计学中的分类数据分析,尤其在独立性检验和关联性分析时。列联表的每个单元格展示了相应分类变量组合的频数(或比例&#xff09…

【2024_CUMCM】LINGO入门+动态规划

目录 什么是动态规划 怎么使用动态规划? 例题:最短路线问题 2020b-问题一 稳定性分析 灵敏度分析 什么是动态规划 基本想法:将原问题转换为一系列相互联系的子问题,然后通过逐层递推求得最后的解 基本思想:解决…

X12端口配置指南:ISA ID、测试指示符与997

通过知行之桥EDI系统实现X12 & 标准XML之间的格式转换时,需要完善交换头ISA ID及其限定符、测试标识符以及997的相关配置。 在X12文件中有两组EDI ID对,分别是发送方 ID 限定符 及发送方ID ,接收方 ID 限定符及接收方ID。 比如&#xf…

STM32Cubemx配置生成 Keil AC6支持代码

文章目录 一、前言二、AC 6配置2.1 ARM ComPiler 选择AC62.2 AC6 UTF-8的编译命令会报错 三、STM32Cubemx 配置3.1 找到stm32cubemx的模板位置3.2 替换文件内核文件3.3 修改 cmsis_os.c文件3.4 修改本地 四、编译对比 一、前言 使用keil ARM compiler V5的时候,编译…

RK3568 buildroot 使用dropbear实现ssh远程的方法

RK3568 buildroot 使用dropbear实现ssh远程的方法 文章目录 RK3568 buildroot 使用dropbear实现ssh远程的方法前言一、创建S99dropbear.sh脚本二、创建sshd_config三、添加root账户密码到系统验证登录前言 rk3568 linux 的sdk中,buildroot已经集成了dropbear的所需的lib库环境…

交替打印-GO

1 两个channel 版本 package mainimport ("fmt""sync")var wg sync.WaitGroup var c1 chan int var c2 chan intfunc A(){defer wg.Done()for i:0;i<10;i {<-c1fmt.Println(2*i)c2<-1 //牵引协程} } func B(){defer wg.Done()for i:0…

Java内存区域与内存溢出异常(自动内存管理)

序言&#xff1a;Java与C之间有一堵由内存动态分配和垃圾收集技术所围成的高墙&#xff0c;墙外面的人想进去&#xff0c;墙里面的人却想出来。 1.1概述 对于从事C、C程序开发的开发人员来说&#xff0c;在内存管理领域&#xff0c;他们既是拥有最高权力的“皇帝”&#xff0c…

使用OpenCV在按下Enter键时截图并保存到指定文件夹

使用OpenCV在按下Enter键时截图并保存到指定文件夹 在这篇博客中&#xff0c;我们将介绍如何使用OpenCV库来实现一个简单的功能&#xff1a;在按下Enter键时从摄像头截图并保存到指定的文件夹中。这个功能可以用于各种应用&#xff0c;例如监控系统、视频捕捉等。 前置条件 …

在FPGA程序中Handshake(握手)和Register(寄存器)区别

在FPGA程序中&#xff0c;Handshake&#xff08;握手&#xff09;和Register&#xff08;寄存器&#xff09;是两种不同的通信和数据传输机制。它们各有特点和适用场景。以下是它们的区别和应用场景的详细解释&#xff1a; Register&#xff08;寄存器&#xff09; 特点&#…

SQLServer用户们,你们摊上大事了!

最近一段时间&#xff0c;我们经常会收到了许多用户的咨询&#xff0c;问我们何时能纳管SQLServer&#xff1f;耐不住小伙伴们的猛烈催促及热切期待&#xff0c;本不想纳管SQLServer的研发团队也抓紧将这项需求提上日程。并在DBdoctor v3.2.2版本中成功实现了对SQLServer的纳管…

班级录取查询系统如何制作

在教育的长河中&#xff0c;我们每位老师都曾面临过这样一个问题&#xff1a;如何高效、准确地完成班级录取查询的任务&#xff1f;记得在以往&#xff0c;每当新学期伊始&#xff0c;我们不得不手忙脚乱地整理学生名单&#xff0c;然后逐一通知他们所在的班级。这个过程不仅耗…

谷歌Google Ads新账号推广方案

第一阶段重点 推广地区优化&#xff1a;分析投放国家的数据&#xff0c;剔除高花费低转化的国家&#xff0c;将预算重新分配给高性价比的国家&#xff0c;从而降低询盘成本并增加询盘数量。关键词优化&#xff1a;识别并暂停或降价高成本低回报的关键词&#xff0c;减少询盘成本…

《mysql篇》--索引事务

索引 索引的介绍 索引是帮助MySQL高效获取数据的数据结构&#xff0c;是一种特殊的文件&#xff0c;包含着对数据表里所有记录的引用指针&#xff0c;因为索引本身也比较大&#xff0c;所以索引一般是存储在磁盘上的&#xff0c;索引的种类有很多&#xff0c;不过如果没有特殊…

[ios-h5]在ios系统浏览器中输入框得到焦点时页面自动放大

问题&#xff1a; 在ios系统浏览器中输入框得到焦点时页面自动放大。 解决&#xff1a; 添加meta标签。 <meta name"apple-mobile-web-app-capable" content"yes" /> <meta name"viewport" content"widthdevice-width, initial-…