[TOC]
# 运行环境
JavaScript 是伴随着浏览器的诞生而诞生,所以 JavaScript 的执行最多还是在浏览器环境之内。但是 JavaScript 作为服务端脚本的概念在诞生之初就有,1995年网景公司就提出了服务端 JavaScript 的概念,并研发了 Netscape Enterprise Server;1996年微软发布的 JScript 也可以运行在服务端。
随着技术的发展各种JavaScript引擎出现,2009年5月Node.js的发布将 JavaScript 作为服务端脚本推向了一个高潮。关于JavaScript服务端的实现可以参看 [List_of_server-side_JavaScript_implementations](https://link.jianshu.com?t=https%3A%2F%2Fen.wikipedia.org%2Fwiki%2FList_of_server-side_JavaScript_implementations)。
JavaScript 的运行不像 C 语言等其他编译型语言编译后直接在操作系统上运行,因为它是脚本语言,运行时必须要借助引擎(解释器)来运行,所以它可以在封装了引擎的环境下运行。
封装了JavaScript引擎的环境可以分为两类,一类是浏览器环境;一类是非浏览器环境,比如 Node.js、MongoDB。我没有采用 wikipedia 中 clent-side 和 server-side 的直接翻译,因为 JavaScript 既可以编写服务端脚本也可以编写 shell 脚本,甚至图形界面应用程序。
把运行环境分为浏览器环境和非浏览器环境是因为他们提供了截然不同的操作模块。
1. 浏览器环境下 JavaScript 由三部分组成,分别是 ECMAScript、DOM 和 BOM,**BOM 和 DOM 是针对浏览器环境所扩展的操作方法**。
2. 非浏览器环境,比如 Node.js ,也是以 ECMAScript 为基础,扩展出了 I/O 操作、文件操作、数据库操作等等;在 MongoDB 中则是可以作为 shell 脚本操作数据库;在 Eclipse e4 中可以编写扩展。
# 运行机制
了解了JavaScript的运行环境,我们来看看运行机制。这里我们不再谈微软的JScript,一方面写本文时我没有找到详尽的介绍JScript的资料,另一方面 JScript 的应用现在不常见。
JavaScript是个什么样子,取决于它初始应用于哪里,它是作为浏览器的脚本出现,主要用途是解决网页中的用户交互。页面中的用户交互行为会让页面中的 DOM 元素产生变化,比如用户输入信息后的反馈提示等等。JavaScript在浏览器环境中操作DOM,为避免复杂的同步问题,决定了它采用单线程。如果同时有多个线程,有的在DOM节点上添加内容,有的修改了整个节点,甚至有的删除了整个节点,这个时候很难判断到底采用哪个线程的结果。
JavaScript最大的特点就是单线程,在浏览器环境中是,在非浏览器环境中同样也是。单线程也就意味着JavaScript在同一时间只能进行一项任务,如果有多项任务的话,需要对任务进行排队,完成一个才能继续下一个。
不同的浏览器、不同的引擎、不同的执行环境,执行 JavaScript 的细节会有差异,但是不变的是单线程和队列。
# 运行过程
在浏览器环境中,JavaScript引擎按标签代码块从上到下的顺序加载并立即解释执行。
我们在这里不探究引擎的详尽解释执行细节,比如词法分析、语法分析以及语法树的构造等等,只说它解释执行过程中非常重要的两个时期预编译期(预解析期)和执行期。理解这两个阶段十分有助于理解 JavaScript 中的一些“奇特”的现象。
在预编译期JavaScript会对 var 和 function 的声明在其所在作用域内进行提升,提升的位置相当于所在作用域开始位置。预编译期需要注意下面几个问题:
1.预编译首先是全局预编译,**函数体在未调用时不进行预编译 **
2.只有 `var` 和 `function` 声明会提升
3.注意是在所在作用域内提升,不会扩展到其他作用域
4.预编译后顺序执行
# 三个阶段
**全面分析js引擎的执行过程,分为三个阶段**
1、语法分析
2、预编译阶段
3、执行阶段
说明:**浏览器先按照js的顺序加载标签分隔的代码块,js代码块加载完毕之后,立刻进入到上面的三个阶段,然后再按照顺序找下一个代码块,再继续执行三个阶段,无论是外部脚本文件(不异步加载)还是内部脚本代码块,都是一样的,并且都在同一个全局作用域中。**
## 语法分析
js 的代码块加载完毕之后,会首先进入到语法分析阶段,该阶段的主要作用:
分析 js 脚本代码块的语法是否正确,如果出现不正确会向外抛出一个**语法错误(syntaxError)**,停止改 js 代码的执行,然后继续查找并加载下一个代码块;如果语法正确,则进入到预编译阶段。
类似的语法报错的如下图所示:
![](https://www.mwcxs.top/static/upload/pics/2019/1/7mAWoi2mhsC1ERUv-8suJB6Tm.png)
## 预编译阶段
js代码块通过语法分析阶段之后,语法都正确的,会进入预编译阶段。
在分析预编译阶段之前,我们先来了解一下 js 的**运行环境**,运行环境主要由三种:
1、全局环境( js 代码加载完毕后,进入到预编译也就是进入到全局环境)
2、函数环境(函数调用的时候,进入到该函数环境,不同的函数,函数环境不同)
3、`eval` 环境(不建议使用,存在安全、性能问题)
每进入到一个不同的运行环境都会创建 一个相应的**执行上下文(execution context)**,那么在一段 `js` 程序中一般都会创建多个执行上下文,`js` 引擎会以栈的数据结构对这些执行进行处理,形成**函数调用栈(call stack),**栈底永远是**全局执行上下文(global execution context)**,栈顶则永远时当前的执行上下文。
# JavaScript到底是编译型还是解释型语言
* JavaScript 代码需要在机器(node或者浏览器)上安装一个工具(JS引擎)才能执行。这是解释型语言需要的。编译型语言产品能够自由地直接运行。
* 声明提升等不是代码修改。在这个过程中没有生成中间代码。这只是JS解释器处理事情的方式。
* JIT 是唯一一点我们可以对JavaScript是否是一个解释型语言提出疑问的理由。但是JIT不是纯粹的编译器,它在执行前进行编译。而且 JIT 只是 Mozilla 和 Google 的开发人员为了在他们的浏览器产品中提升性能才引入的。JavaScript 或 TC39 从来没有要求这样做。
更多关于JIT的事情你可以阅读 Lin Clarks 的[关于JIT的课程](https://hacks.mozilla.org/2017/02/a-crash-course-in-just-in-time-jit-compilers/)。
因此,虽然 JavaScript 执行时像是编译过的或者是一种混合,但是我仍然认为说 JavaScript 是一个解释型语言要好过说它是一个编译型语言或者很多人说的一个混合型的语言。
## 参考
[浏览器环境概述](http://javascript.ruanyifeng.com/bom/engine.html)
[javascript引擎执行的过程的理解--语法分析和预编译阶段](https://www.cnblogs.com/chengxs/p/10240163.html)
[JavaScript 编译原理、编译器、引擎及作用域](https://www.jianshu.com/p/5ebf2ad6def2)
[JavaScript运行环境、运行机制与运行过程](https://www.jianshu.com/p/47ce0a9def7b)
- 步入JavaScript的世界
- 二进制运算
- JavaScript 的版本是怎么回事?
- JavaScript和DOM的产生与发展
- DOM事件处理
- js的并行加载与顺序执行
- 正则表达式
- 当遇上this时
- Javascript中apply、call、bind
- JavaScript的编译过程与运行机制
- 执行上下文(Execution Context)
- javascript 作用域
- 分组中的函数表达式
- JS之constructor属性
- Javascript 按位取反运算符 (~)
- EvenLoop 事件循环
- 异步编程
- JavaScript的九个思维导图
- JavaScript奇淫技巧
- JavaScript:shim和polyfill
- ===值得关注的库===
- ==文章==
- JavaScript框架
- Angular 1.x
- 启动引导过程
- $scope作用域
- $q与promise
- ngRoute 和 ui-router
- 双向数据绑定
- 规范和性能优化
- 自定义指令
- Angular 事件
- lodash
- Test