JavaScript究竟是解释型语言仍是编译型语言?

几天前一个刚接触 JavaScript 的朋友问我 JavaScript 是编译型语言仍是解释型语言。从一个初学者那里听到这样的问题让我有些惊讶,由于全部初学者都知道 JS 是一个解释型语言;特别是像她这样以前使用过 Java 这类语言的初学者。node

当一些人深刻 JavaScript 而且开始研究 V8 引擎、SpiderMonkey、JIT 之类东西的时候,他们开始对于解释型仍是编译型有更多的疑问。很高兴看到她已经在这个阶段了。编程

使人困惑的是什么?

最开始的时候,JavaScript 的圣经 —— MDN 明确地说 JavaScript 是一个解释型语言(同时还说到了 JIT 及时编译,后文会说起)。可是下面几点仍然会让 JavaScript 是否真的是一个解释型语言产生疑问:浏览器

  • 若是 JS 是解释型语言那为何会有变量提高(hoisting)?
  • JIT(及时编译)会作代码优化(同时建立代码的编译版本);解释型语言没法作到这些。

有什么快速的回答吗?

因为 JavaScript 规范没有对这一点作明确说明,困惑和疑问是都是存在的,不能片面地回答。让咱们基于理论定义和 JavaScript 工做流程来弄清楚 JavaScript 究竟是什么语言。编程语言

编译型语言 VS 解释型语言

主要问题是没有团体或者组织规定这些;例如:编译型语言和解释型语言的定义以及如何划分。 而这两个都是概念。ide

因此根据概念,编译型语言是代码在运行前编译器将人类能够理解的语言(编程语言)转换成机器能够理解的语言。函数

解释型语言也是人类能够理解的语言(编程语言),也须要转换成机器能够理解的语言才能执行,可是是在运行时转换的。因此执行前须要环境中安装了解释器;可是编译型语言编写的应用在编译后能直接运行。工具

许多人认为解释型语言意味着当遇到程序中行号为xyz时直接将其传给CPU就能运行;可是事实不是这样。全部的编程语言都是为人类建立的。他们是人类可以理解的。必须将编程语言转换为机器语言才能运行。编译器获取整个代码,转换它,作合适的优化而且建立一个能够运行的输出文件。编译器根据上下文来转换语句。性能

那么变量提高呢?

我以为你应该已经知道了 JavaScript 的变量提高。在函数做用域内的任何变量的声明都会被提高到顶部而且值为undeinfed优化

因此 JavaScript 引擎好像解释了同一个脚本文件两次?第一次完成全部的声明提高而后第二次才执行代码?仍是先编译整个代码而后运行它?这两种都不对。网站

下面是 JavaScript 处理声明语句的过程:

  • 一旦 V8 引擎进入一个执行具体代码的执行上下文(函数),它就对代码进行词法分析或者分词。这意味着代码将被分割成像foo = 10这样的原子符号(atomic token)。
  • 在对当前的整个做用域分析完成后,引擎将 token 解析翻译成一个AST(抽象语法树)。
  • 引擎每次遇到声明语句,就会把声明传到做用域(scope)中建立一个绑定。每次声明都会为变量分配内存。只是分配内存,并不会修改源代码将变量声明语句提高。正如你所知道的,在JS中分配内存意味着将变量默认设为undefined
  • 在这以后,引擎每一次遇到赋值或者取值,都会经过做用域(scope)查找绑定。若是在当前做用域中没有查找到就接着向上级做用域查找直到找到为止。
  • 接着引擎生成 CPU 能够执行的机器码。
  • 最后, 代码执行完毕。

因此变量提高不过是执行上下文的小把戏,而不是许多网站描述的源代码修改。在执行任何语句以前,解释器就要从建立执行上下文后已经存在的做用域(scope)中找到变量的值。

解释 JavaScript 中的即时编译(JIT)

JIT 或 及时编译 编译器不是 JavaScript 所特有的。其余语言好比 Java 也有一些在执行前编译代码的机制。

现代 JavaScript 引擎一样有 JIT。是的,它们有编译器。让我来为你解释一下为何它们须要 JIT 以及 JIT 在 JavaScript 的执行中是如何工做的。

编译型和解释型语言最重要的区别是编译型语言须要很长的时间来准备执行。由于它须要对整个代码进行词法分析、作一些极致的优化等工做。另外一方面解释型语言几乎在执行后一瞬间就开始,可是没有任何代码优化。因此每一条语句都是分开转换(编译)的,考虑下面这一段代码。

for(i=0; i < 1000; i++){
    sum += i;
}

在编译型语言中sum += i部分在循环运行时已经编译成了机器码,机器码将直接运行一千次。

可是在解释型语言中,执行时会将sum += i转换(编译)一千次。对相同的代码进行一千次转换会形成很是大的性能损耗。

这就是 Google 和 Mozilla 的开发人员将 JIT 加入 JavaScript 的缘由。

编译

在 JavaScript 中若是一段代码运行超过一次,那么就称为 warm。若是一个函数开始变得 warmer(译者注:即运行更屡次),JIT 将把这段代码送到编译器中编译而且保存一个编译后的版本。下一次一样代码执行的时候,引擎会跳过翻译过程直接使用编译后的版本。

这将优化性能。在真正的编译器中,由于编译器能访问整个代码因此作了除此以外更多的事情。

优化

若是一段 warm 的代码变得 hot 或者 hotter(译者注:指运行更屡次以及比更多还要多的次数)JIT 会尝试更多的优化而且保存优化后的版本。在编译器进行优化的过程当中会作一些关于变量类型和运行环境中值的假设,若是假设不成立就将这个优化的版本回退,若是假设成立的话,这将让代码性能更高。

想要了解更多 JIT 的知识能够阅读 Lin Clarks 关于JIT的课程

总结

如今咱们了解了 JavaScript 执行时到底发生了什么,因此应该能够区分 JavaScript 究竟是编译型仍是解释型语言了。下面是这篇文章的要点。

  • JavaScript 代码须要在机器(node 或者浏览器)上安装一个工具(JS 引擎)才能执行。这是解释型语言须要的。编译型语言程序可以自由地直接运行。
  • 变量提高不是代码修改。在这个过程当中没有生成中间代码。变量提高只是 JS 解释器处理事情的方式。
  • JIT 是惟一一点咱们能够对 JavaScript 是不是一个解释型语言提出疑问的理由。可是 JIT 不是完整的编译器,它在执行前进行编译。并且 JIT 只是 Mozilla 和 Google 的开发人员为了提高浏览器性能才引入的。JavaScript 或 TC39 历来没有强制要求使用 JIT。

所以,虽然 JavaScript 执行时像是在编译或者像是一种编译和解释的混合,我仍然认为 JavaScript 是一个解释型语言或者是一个今天不少人说的混合型语言,而不是编译型语言。

相关文章
相关标签/搜索