JavaScript的解释引擎

JavaScript解释引擎

看完本文,你就可以理解下面两个实例的原理!

fun()
console.log(num)

function fun() {
    num = 20
}

// output: 20
var num = 10
fun()

function fun() {
    console.log(num)
    var num = 20
}

// output: undefined
一、背景
1、 什么是JavaScript解析引擎?

就是能够“读懂”JavaScript代码,并准确地给出代码运行结果的一段程序。比方说,当你写了 ​var a = 1 + 1; ​这样一段代码 JavaScript引擎做的事情就是看懂(解析)你这段代码,并且将a的值变为2

2、JavaScript解析引擎与浏览器又是什么关系?

简单地说,JavaScript引擎是浏览器的组成部分之一。因为浏览器还要做很多别的事情,比如解析页面、渲染页面、Cookie管理、历史记录等等。那么,既然是组成部分,因此一般情况下JavaScript引擎都是浏览器开发商自行开发的。比如:IE9的Chakra、Firefox的TraceMonkey、Chrome的V8等等

二、概念
2.1 编译器(Compiler)

简单的说,就是将一种语言翻译成另一种计算机或虚拟机可以直接执行的语言(通常为机器语言,例如JAVA编译器,输出的是.class二进制文件,可被JVM解读)[百科]

2.2 机器语言(machine language)

机器语言是一种指令集的体系。这种指令集由二进制代码组成,称为机器码。它们可以被计算机的CPU直接解读并执行[百科]

2.3 编译型语言(Compiled language)

通过编译器产出的代码:如C、C++等

2.4 反编译器

编译器的逆向过程[百科]

2.5 解释器

目的:跨平台
和编译器一样,将一种语言翻译成另一种计算机或虚拟机可以直接执行的语言(通常为机器语言)
与编译器不同的是,解释器是在程序运行时,边运行,边转译,生成计算机可以执行的代码
而编辑器则是预先就把计算机可直接执行的代码转译生成完了,生成计算机可以执行的代码

2.6 解释型语言

通过解释器执行的代码:如javascript等

2.7 JAVA类语言是编译型语言还是解释型语言?

JAVA类语言属于编译解释型语言
JAVA从源代码到最终计算机可执行程序的过程:源代码——(编译器).class二进制文件——(JVM[解释器 + JIT])机器语言


三、编译

编译的过程就是:code -> to -> code 的过程

源码要运行,必须先转成二进制的机器码,这是编译器的任务
在这里插入图片描述
分词(Tokenizing)| 词法分析(Lexing)
这个过程会把字符串分解成有意义的代码块,这些代码块被称为词法单元

分词(tokenizing)和词法分析(Lexing)之间的区别 是非常微妙、晦涩的, 主要差异在于词法单元的识别是通过有状态还是无状态的方式进行的

简单来说,如果词法单元生成器在判断 a 是一个独立的词法单元还是其他词法 单元的一部分时,调用的是有状态的解析规则,那么这个过程就被称为词法分析

解析 | 语法分析(Parsing)

这个过程是将词法单元流(数组)转换成一个由元素逐级嵌套所组成的代表了程序语法 结构的树。这个树被称为 “抽象语法树”(Abstract Syntax Tree,AST)

代码生成

将AST转化为可执行代码的过程(这个过程与语言、目标平台息息相关)

简单来说:就是有某种方法可以将 var a = 2 的 AST 转化为一组机器指令,用来创建一个叫做 a 的变量(包括分配内存等),并将一个值存储在 a 中
在这里插入图片描述
AST

如果你查看目前任何主流的项目中的​devDependencies​,会发现前些年的不计其数的插件诞

我们归纳一下有:javascript转译、代码压缩、css预处理器、elint、pretiier,等

有很多js模块我们不会在生产环境用到,但是它们在我们的开发过程中充当着重要的角色

所有的上述工具,不管怎样,都建立在了AST这个巨人的肩膀上
在这里插入图片描述

AST

在这里插入图片描述

词法分析

在这里插入图片描述

语法解析

在这里插入图片描述

1、比起那些编译过程只有三个步骤的语言的编译器,JavaScript引擎要复杂得多
(在语法分析和代码生成阶段有特定的步骤来对运行时性能优化,包括冗余元素进行优化)

2、对于JavaScript来说,大部分情况下编译发生在执行前几微秒(甚至更短的时间)
JavaScript引擎用尽各种办法(JIT,可以延迟编译甚至实施重编译)来保证性能最佳


四、作用域

The current context of execution

作用域是一套规则,用于确定在何处(存储位置)以及如何查找变量(标识符)

在这里插入图片描述

在这里插入图片描述

引擎

从头到尾负责整个 JavaScript 程序的编译及执行过程

编译器

引擎的好朋友之一,负责语法分析及代码生成等脏活累活

JavaScript引擎它到底算是个解释器还是个编译器,因为,比如像V8(Chrome的JS引擎),它其实为了提高JS的运行性能,在运行之前会先将JS编译为本地的机器码(native machine code),然后再去执行机器码(这样速度就快很多)

作用域

引擎的另外一个好朋友,负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限。

例子:

var a = 1
// 编译器
// 1.
// 遇到 var a,编译器会询问作用域是否已经有一个该名称的变量存在于一个作用域的集合中。
// 是:忽略该声明,继续进行编译  否:要求作用域在当前作用域集合中声明一个新的变量,并命名为a

// 2.
// 编译器会为引擎生成运行时所需的代码,这些代码被用来处理 a = 1 这个赋值操作.

// 引擎
// 引擎运行时会首先询问作用域,在当前作用域集合中是否存在一个叫做 a 的变量
// 是:就会使用这个变量  否:引擎会继续查找该变量
// 最终:如果找到a变量就赋值1给它,否则会抛出异常

变量的赋值操作会执行两个动作,首先编译器会在当前作用域中声明一个变量(如果之前没有声明过),然后在运行时引擎会在作用域中查找该变量,如果能找到就对它赋值

编译器在编译过程的第二步中生成了代码,引擎执行它时,会通过查找变量 a 来判断它是 否已声明过。查找的过程由作用域进行协助,但是引擎执行怎样的查找,会影响最终的查 找结果

引擎的查找方式

  • LHS: 赋值操作左侧(赋值操作的目标是谁 D)
  • RHS: 赋值操作右侧(赋值操作的源头是谁 S)
function foo(a) { // LHS
    console.log(a) // RHS
}
foo(2) // RHS

// foo(2) RHS foo: 去找到 foo的值并把值赋予我 (...):foo的值需要被执行,最好是一个函数类型
// a = 2  LHS (隐式赋值)  为 = 2,这个赋值操作找到目标
// console.log(a)  RHS 本身也需要一个引用才能执行,因此会对 console 对象进行 RHS 查询,并且检查 得到的值中是否有一个叫作 log 的方法

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

声明提升

console.log(a) // undefined
var a = 1
// 说明:大部分编程语言都是先声明变量再使用,但在JS中,事情有些不一样。因为声明提升(hoisting)

在JavaScript中,一个变量名进入作用域的方式有 4 种:

函数声明和变量声明总是会被移动(即hoist)到它们所在的作用域的顶部(这对你是透明的)
而变量的解析顺序(优先级),与变量进入作用域的4种方式的顺序一致

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值