JS编译原理,LHS与RHS查询,作用域
文章来源:https://wflynn.cn/pages/0b0205/ 作者::Miofly
传统编译语言的流程
通常将 JavaScript 归类为“动态”或“解释执行”语言,但事实上它是一门编译语言。
一般会经历三个步骤,统称为“编译”。
分词/词法分析
这个过程会将由字符组成的字符串分解成(对编程语言来说)有意义的代码块,这些代码块被称为词法单元(token
)。例如,考虑程序 var a = 2;
。这段程序通常会被分解成为下面这些词法单元:var
、a
、=
、2
、;
。空格是否会被当作词法单元,取决于空格在这门语言中是否具有意义。
解析/语法分析
这个过程是将词法单元流(数组)转换成一个由元素逐级嵌套所组成的代表了程序语法结构的树。这个树被称为“抽象语法树 ”(Abstract Syntax Tree
,AST
)。var a = 2
; 的抽象语法树中可能会有一个叫作 VariableDeclaration
的顶级节点,接下 来是一个叫作 Identifier
(它的值是 a
)的子节点,以及一个叫作 AssignmentExpression
的子节点。AssignmentExpression
节点有一个叫作 NumericLiteral
(它的值是 2
)的子节点。
代码生成
将 AST
转换为可执行代码的过程称被称为代码生成。这个过程与语言、目标平台等息息相关。抛开具体细节,简单来说就是有某种方法可以将 var a = 2
; 的 AST
转化为一组机器指令,用来创建一个叫作 a
的变量(包括分配内存等),并将一个值储存在 a
中。
作用域
JavaScript
的编译过程不是发生在构建之前的。 对于JavaScript
来说,大部分情况下编译发生在代码执行前的几微秒(甚至更短!)的时间内。在我们所要讨论的作用域背后,JavaScript
引擎用尽了各种办法(比如 JIT,可以延迟编译甚至实施重编译)来保证性能最佳。 简单地说,任何JavaScript
代码片段在执行前都要进行编译(通常就在执行前)。因此,JavaScript
编译器首先会对var a = 2
; 这段程序进行编译,然后做好执行它的准备,并且通常马上就会执行它。
- 引擎
从头到尾负责整个
JavaScript
程序的编译及执行过程。 - 编译器 负责语法分析及代码生成等脏活累活
- 作用域 负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限 用来管理引擎如何在当前作用 域以及嵌套的子作用域中根据标识符名称进行变量查找。。
javascript 对 var a = 2 这段代码的编译运行分析
编译器首先会将这段程序分解成词法单元,然后将词法单元解析成一个树结构,也就是上面提到的分词以及解析。
编译器开始进行代码生成时的处理如下
- 遇到
var a
,编译器会询问作用域是否已经有一个该名称的变量存在于同一个作用域的集合中。如果是,编译器会忽略该声明,继续进行编译;否则它会要求作用域在当前作用域的集合中声明一个新的变量,并命名为a
。 - 接下来编译器会为引擎生成运行时所需的代码,这些代码被用来处理
a = 2
这个赋值操作。引擎运行时会首先询问作用域,在当前的作用域集合中是否存在一个叫作a
的 变量。如果是,引擎就会使用这个变量;如果否,引擎会继续查找该变量如果引擎最终找到了a
变量,就会将2
赋值给它。否则引擎就会抛出一个异常!
总结:变量的赋值操作会执行两个动作,首先编译器会在当前作用域中声明一个变量(如果之前没有声明过),然后在运行时引擎会在作用域中查找该变量,如果能够找到就会对它赋值。
什么是 LHS 与 RHS 查询
最简单的理解可以概括如下:如果查找的目的是对变量进行赋值,那么就会使用 LHS
查询;如果目的是获取变量的值,就会使用 RHS
查询。
分析 var a = 2 是什么查询
var a = 2
,很显然是将 2
赋值给变量 a
,所以这是 LHS
查询
再分析下面几个列子
例一
- 首先
(a)
中的 a 我们要获取a
这个变量的值,这时就需要执行RHS
查询 - 再者
console.log()
本身也需要一个引用才能执行,这时候我们还需要获取console
的引用,所以要对console
执行RHS
查询。当我们查询到console
的引用后,会检查得到的值中是否有一个叫做log
的方法 这里不会再对log
进行RHS
查询。因为对console
查询完毕后,对象属性访问规则会接管对log
属性的访问。对象会接管其下面的属性的访问
例二
function foo(fnn) {
console.log(fnn);
};
foo('girl');
- 首先
foo(...)
我们需要找到它的引用,这时就需要执行RHS
查询 - 然后我们隐式的将
girl
这个值赋值给了函数参数中的fnn
这个变量,这时就需要执行LHS
查询 - 最后对
console.log(fnn)
的分析就和例一一样,对fnn
进行RHS
查询,对console.log
进行RHS
查询
例三
function foo(fnn) {
var wfly = fnn;
return fnn + wfly;
}
var c = foo('girl');
- 首先
var c = foo('girl')
中,foo(...)
我们需要找到它的引用,这时就需要执行RHS
查询,然后将值赋值给c
,执行LHS
查询 - 然后我们隐式的将
girl
这个值赋值给了函数参数中的fnn
这个变量,这时就需要执行LHS
查询 - 对
var wfly = fnn
; 首先,获取fnn
变量的值需要执行一次RHS
查询,接着将fnn
变量的值赋值给wfly
变量,执行LHS
查询 - 最后
return fnn + wfly
需要对fnn
执行RHS
查询,同样的对wfly
也要执行RHS
查询
所以共计执行了
4
次RHS
查询,3
次LHS
查询
相关文章
- js 正则是否包含某些字符串_js判断字符串中是否包含某个字符串[通俗易懂]
- JS prototype作用
- js定时器与延时器_JS做定时器倒计时
- js生成二维码原理_二维码生成器原理
- 原生 JS 手写一个优雅的图片预览功能,带你吃透背后原理
- js中settimeout()的用法详解_低噪放工作原理
- js跨域请求的三种方法_jQuery
- vue.js客服系统实时聊天项目开发(十)过滤xss字符内容-防止xss攻击
- 使用CDN加载jQuery.js
- 用SQL Server和JS实现数据库管理技术(sqlserver.js)
- 数据JS技术实现实时获取Oracle数据(js实时获取oracle)
- 从前端JS里请求Redis资源,搭建高性能应用(前端js请求redis)
- css+js下拉菜单
- CSS和JS标签style属性对照表(方便js开发的朋友)
- js利用className得到对象的实现代码
- js延迟加载改变JS的位置加快网页加载速度
- 通过JS自动隐藏手机浏览器的地址栏实现原理与代码
- 原生Js页面滚动延迟加载图片实现原理及过程
- 原生Js实现元素渐隐/渐现(原理为修改元素的css透明度)
- js加载读取内容及显示与隐藏div示例
- js数组方法扩展实现数组统计函数
- js动态修改整个页面样式达到换肤效果
- node.js中RPC(远程过程调用)的实现原理介绍
- js实现div闪烁原理及实现代码
- 使用JS获取当前地理位置方法汇总