虽然通常将js归类为“动态”或“解释执行”语言,但其实也可把它看成是一门编译语言。只不过这个所谓的编译与传统的编译语言不同,它不是提前编译的,编译结果也不能在分布式系统中进行移植。对于js来说,它的编译过程不是发生在构建之前的,大部分情况下编译发生在代码执行前的几微秒甚至更短的时间内。甚至是代码执行中

为甚么怀疑js不是解释型语言?

如果是解释型语言,变量声明提升为什么会发生?
JIT(及时编译)做代码优化(同时生成编译版本);解释型语言无法做到这些(解释型语言几乎在执行后一瞬间就开始,几乎没有任何代码优化)

编译型语言 vs 解释型语言

编译型语言是代码在运行前编译器将人类可以理解的语言(编程语言)转换成机器可以理解的语言
解释型语言也是人类可以理解的语言(编程语言),也需要转换成机器可以理解的语言才能执行,但是是在运行时转换的。所以执行前需要解释器安装在环境中;但是编译型语言编写的应用在编译后能直接运行。 许多人认为解释型语言意味着当遇到程序中行号为xyz时直接将其传给CPU就能运行;但是事实不是这样。所有的编程语言都是为人类创建的。他们是人类能够理解的。你必须将编程语言转换为机器语言。编译器获取整个代码,转换它,做合适的优化并且创建一个可以运行的输出文件。编译器根据上下文来转换语句。

声明提升

js的声明提升:在函数作用域内的任何变量的声明都会被提升到顶部并且是undeinfed值。
所以JavaScript引擎解释同样的脚本文件两次?做完所有的声明提升然后执行代码?还是先编译整个代码然后运行它?这两种情况都不对。 下面是js处理声明的过程:
一旦V8引擎进入一个执行具体代码的执行上下文(函数),它就对代码进行词法分析或者分词。这意味着代码将被分割成像foo = 10这样的原子标记。
在对当前的整个作用域分析完成后,引擎将解析成一个AST(抽象语法书)的翻译版本。
引擎每一次遇到声明,它就把声明传到作用域来创建一个绑定。对每一次声明它都会为变量分配内存。只是分配内存而不是把代码修改成声明提升。正如你所知道的,在JS中分配内存意味着将默认值设为undefined。
在这之后,引擎每一次遇到赋值或者取值,它都会通过作用域查找绑定。如果在当前作用域中没有查找到就接着向上级作用域查找直到找到为止。
接着引擎生成CPU可以执行的机器码。
最后, 代码执行完毕。 所以变量提升不过是执行上下文的游戏,而不是网站描述的代码修改。在执行任何语句之前,解释器就已经从运行上下文创建的作用域中找到变量的值了。

进一步补充,js对程序var a=2的处理过程

基本术语介绍
1.分词/词法分析
这些代码块被称为词法单元(token)。例如,var a = 2;。这段程序通常会被分解成为下面这些词法单元:var、a、=、2 、; 2.解析/语法分析
这个过程是将词法单元流(数组)转换成一个由元素逐级嵌套所组成的代表了程序语法结构的树。这个树被称为“抽象语法树”(Abstract Syntax Tree,AST) 3.代码生成
将AST转换为可执行代码的过程称被称为代码生成 。
参与该过程角色
引擎
从头到尾负责整个JavaScript程序的编译及执行过程。 编译器
引擎的好朋友之一,负责语法分析及代码生成等脏活累活。 作用域
引擎的另一位好朋友,负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非常严格的规则,确定当前执行的代码对这些标识符的访问权限
工作流程
编译器首先将这段程序分解成词法单元,然后将词法单元解析成一个树结构。
当编译器进行代码生成时: 1.遇到var a,编译器会询问作用域是否已经有一个该名称的变量存在同一个作用域的集合中。如果是,编译器会忽略该声明,继续进行编译;否则它会要求作用域在当前作用域的集合中声明一个新的变量,并命名为a 2.接下来编译器会为引擎生成运行时所需的代码,这些代码被用来处理a=2这个赋值操作。引擎运行时会首先询问作用域,在当前的作用域集合中是否存在一个叫作a的变量。如果是,引擎就会使用这个变量;如果否,引擎会继续查找该变量。如果引擎最终找到了a变量,就会将2赋值给它。否则就会抛出异常 总结:
变量的赋值操作会执行两个动作,首先编译器会在当前作用域中声明一个变量(如果之前没声明过),然后在运行时引擎会在作用域中查找该变量,如果能够找到就会对它进行赋值。

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

IT或者说及时编译编译器不是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。如果一个函数开始变得更加warm(译者注:运行更多次),JIT将把这段代码送到编译器中编译并且保存一个编译后的版本。下一次同样代码执行的时候,引擎会跳过翻译过程直接使用编译后的版本。

这将优化性能。在真正的编译器中,因为编译器能访问整个代码所以能做更多的事。
优化
如果一段warm代码变得hot或者hotter(译者注:指运行更多次以及比更多还要多的次数)JIT会尝试更多的优化并且保存优化后的版本。在编译器进行优化的过程中会做一些关于变量类型和环境中值的假设;但是如果假设不成立就将这个优化的版本回退,如果假设成立的话,这将让代码性能更高。
总结
JavaScript代码需要在机器(node或者浏览器)上安装一个工具(JS引擎)才能执行。这是解释型语言需要的。编译型语言产品能够自由地直接运行。
声明提升等不是代码修改。在这个过程中没有生成中间代码。这只是JS解释器处理事情的方式。
JIT是唯一一点我们可以对JavaScript是否是一个解释型语言提出疑问的理由。但是JIT不是纯粹的编译器,它在执行前进行编译。而且JIT知识Mozilla和Google的开发人员为了在他们的浏览器产品中提升性能才引入的。JavaScript或TC39从来没有要求这样做

参考链接:

https://segmentfault.com/a/1190000013126460

参考书籍:

《你不知道的js》

js编译原理(你不知道的javascript)的更多相关文章

  1. js 编译原理

    引擎:从头到尾负责整个javaScript 程序的编译过程和执行过程. 编译器: 负责语法分析以及代码的生成. 作用域:负责收集并维护由所有声明的标识符(变量)组成的一系列查询, 并实施一套非常严格的 ...

  2. 前端与编译原理——用JS写一个JS解释器

    说起编译原理,印象往往只停留在本科时那些枯燥的课程和晦涩的概念.作为前端开发者,编译原理似乎离我们很远,对它的理解很可能仅仅局限于"抽象语法树(AST)".但这仅仅是个开头而已.编 ...

  3. 翻译连载 |《你不知道的JS》姊妹篇 |《JavaScript 轻量级函数式编程》- 引言&前言

    原文地址:Functional-Light-JS 原文作者:Kyle Simpson-<You-Dont-Know-JS>作者 译者团队(排名不分先后):阿希.blueken.brucec ...

  4. JS闭包—你不知道的JavaScript上卷读书笔记(二)

    关于闭包,初学者会被绕的晕头转向,在学习的路上也付出了很多精力来理解. 让我们一起来揭开闭包神秘的面纱. 闭包晦涩的定义 看过很多关于闭包的定义,很多讲的云里雾里,晦涩难懂.让不少人以为闭包是多么玄乎 ...

  5. 前端与编译原理 用js去运行js代码 js2run

    # 前端与编译原理 用js去运行js代码 js2run 前端与编译原理似乎相隔甚远,各种热门的框架都学不过来,那能顾及到这么多底层呢,前端开发者们似乎对编译原理的影响仅仅是"抽象语法树&qu ...

  6. 你不知道的Javascript(上卷)读书笔记之一 ---- 作用域

    你不知道的Javascript(上卷)这本书在我看来是一本还不错的书籍,这本书用比较简洁的语言来描述Js的那些"坑",在这里写一些博客记录一下笔记以便消化吸收. 1 编译原理 在此 ...

  7. 你不知道的javaScript上卷(第一章 作用域是什么)

    在写这篇博客时这本书我已经是看过一遍了,为了加深印象和深入学习于是打算做这系列的前端经典书籍导读博文,大家如果觉得这本书讲的好可以自己买来看看,我是比较喜欢看纸质版书的,因为这样才有读书的那种感觉. ...

  8. 读《你不知道的JavaScript(上卷)》后感-浅谈JavaScript作用域(一)

    原文 一. 序言 最近我在读一本书:<你不知道的JavaScript>,这书分为上中卷,内容非常丰富,认真细读,能学到非常多JavaScript的知识点,希望广大的前端同胞们,也入手看看这 ...

  9. 《你不知道的javascript》读书笔记1

    概述 放假读完了<你不知道的javascript>上篇,学到了很多东西,记录下来,供以后开发时参考,相信对其他人也有用. js的工作原理 引擎:从头到尾负责整个js的编译和运行.(很大一部 ...

随机推荐

  1. mybatis 使用事务处理

    mybatis默认开启事务 以前使用JDBC的时候,如果要开启事务,我们需要调用conn.setAutoCommit(false)方法来关闭自动提交,之后才能进行事务操作,否则每一次对数据库的操作都会 ...

  2. 常见的页面中两个div自适应等高CSS控制

    第一种利用dispaly:table,父级div设置dispaly:table子级div设置display: table-cell; 第一种利用dispaly:flex,父级div设置dispaly: ...

  3. 修改Linux的编码集

    使用SSH secure远程连接linux时,查看linux里的内容, 发现乱码.这是由于SSH secure客户端的编码是gbk ,而 linux的默认编码是utf-8 要解决乱码问题,必须将lin ...

  4. 【模板】2-SAT 问题

    [传送门] 分析 按照逻辑关系建图,跑tarjan,如果上下点在一个环中,说明不可能,不然就可能. 代码 #include <bits/stdc++.h> #define ll long ...

  5. 【转】Esp8266学习之旅① 搭建开发环境,开始一个“hellow world”串口打印。

    @2019-02-28 [小记] Esp8266学习之旅① 搭建开发环境,开始一个“hellow world”串口打印.

  6. LVM备份(2)-创建LVM逻辑卷

    懵逼了几天,对LVM快照备份总算有了个比较清晰的认识 总的来说,就是这样: 1.普通分区备份比较困难,需要转化成LVM逻辑卷进行管理 2.在新创建的LVM逻辑卷上进行业务操作,比如建数据库 3.某个时 ...

  7. 一文学会matplotlib

    matplotlib基础 “““ 假设一天中每隔两个小时(range(2,26,2))的气温(℃)分别是[15,13,14.5,17,20,25,26,26,27,22,18,15] 用matplot ...

  8. 1.2浅谈Spring-Spring结构

    时隔很多天的我又回来....最近发展了一下自己的爱好,所以拖了很长时间. 前面我们从概念性上分析了spring的特性 这里我们附上Spring框架的结构图 我们简单的来说一些这个框架图 我们从下往上看 ...

  9. JVM调优命令-jhat

    jhat JVM Heap Analysis Tool命令是与jmap搭配使用,用来分析jmap生成的dump,jhat内置了一个微型的HTTP/HTML服务器,生成dump的分析结果后,可以在浏览器 ...

  10. Android App性能测试之二:CPU、流量

    CPU---监控值的获取方法.脚本实现和数据分析 1.获取CPU状态数据 adb shell dumpsys cpuinfo | findstr packagename 自动化测试脚本见cpustat ...