我们前几章和讲解了什么浏览器的组成部分以及渲染引擎, 今天我们主要讲一下 JS 引擎的相关知识点, 那么在开讲之前我们需要回顾一下有关渲染引擎的相关知识点
渲染引擎
关键渲染路径是指浏览器从最初接收请求来的 html,CSS,JavaScript 等资源, 然后解析, 构建树, 渲染布局, 绘制, 最后呈现给客户能看到的界面这整个过程.
image.PNG
JavaScript 引擎
JavaScript 引擎是一个专门处理 JavaScript 脚本的虚拟机, 一般会附带在网页浏览器中. JavaScript 引擎从头到尾负责整个 JavaScript 程序的编译和执行过程. JS 的引擎有很多种, 而最为大家熟知的无疑是 V8 引擎, 他用于 Chrome 浏览器和 Node 中.
2019-06-19-13-00-37
V8 引擎由两个主要部件组成:
emory Heap(内存堆)-内存分配地址的地方
Call Stack(调用堆栈) - 代码执行的地方
上面只是简单的对 JS 引擎进行一下基本的了解, 下面开始正式介绍 JS 引擎的执行过程(以 V8 引擎为例)
JS 引擎执行过程
全面分析 JS 引擎的执行过程, 主要分为三个阶段:
1. 语法分析
2. 预编译阶段
3. 执行阶段
下面着重讲一下这三个阶段:
语法分析
分析该 JS 脚本代码块的语法是否正确, 如果出现不正确, 则向外抛出一个语法错误(SyntaxError), 停止该 JS 代码块的执行, 然后继续查找并加载下一个代码块; 如果语法正确, 则进入预编译阶段
预编译阶段
JS 代码块通过语法分析阶段之后, 语法都正确的下回进入预编译阶段.
在分析预编译阶段之前, 我们先来了解一下 JS 的运行环境, 运行环境主要由三种:
1, 全局环境(JS 代码加载完毕后, 进入到预编译也就是进入到全局环境)
2, 函数环境(函数调用的时候, 进入到该函数环境, 不同的函数, 函数环境不同)
3,eval 环境(不建议使用, 存在安全, 性能问题)
每进入到一个不同的运行环境都会创建一个相应的执行上下文(execution context)「下文会介绍」, 那么在一段 JS 程序中一般都会创建多个执行上下文, JS 引擎会以栈的数据结构对这些执行进行处理, 形成函数调用栈(call stack), 栈底永远是全局执行上下文(global execution context), 栈顶则永远时当前的执行上下文.
注意: 执行上下文的相关概念会在下面进一步进行详细介绍
执行阶段
在执行阶段, 我们暂时先不考虑异步(因为异步阶段涉及到的知识点是事件循环[event loop] ), 等读完这篇文章之后并且理解之后, 去看我写的深入理解事件循环这篇文章, 就会进一步理解.
我们上文讲到 V8 引擎由两个主要部件组成:
emory Heap(内存堆)-内存分配地址的地方
Call Stack(调用堆栈)- 代码执行的地方
我们声明的函数与变量被储存在『内存堆』中, 而当我们要执行的时候, 就必须借助于『调用栈』来解决问题. 函数调用栈就是使用栈存取的方式进行管理运行环境, 特点是先进后出, 后进后出
我们来分析一下 JS 代码来理解函数调用栈:
- function bar() {
- var B_context = "bar saucxs";
- function foo() {
- var f_context = "foo saucxs";
- }
- foo()
- }
- bar()
上面代码块通过语法分析后, 进入预编译阶段创建执行上下文, 如图所示
image.PNG
1, 首先进入到全局环境, 创建全局执行上下文(global Execution Context ), 推入到 stack 中;
2, 调用 bar 函数, 进入 bar 函数运行环境, 创建 bar 函数执行上下文(bar Execution Context), 推入 stack 栈中;
3, 在 bar 函数内部调用 foo 函数, 则再进入到 foo 函数运行环境中, 创建 foo 函数执行上下文(foo Execution Context), 如上图, 由于 foo 函数内部没有再调用其他函数, 那么则开始出栈;
5,foo 函数执行完毕之后, 栈顶 foo 函数执行上下文 (foo Execution Context) 首先出栈;
6,bar 函数执行完毕, bar 函数执行上下文 (bar Execution Context) 出栈;
7, 全局上下文 (global Execution Cntext) 在浏览器或者该标签关闭的时候出栈.
说明: 不同的运行环境执行都会进入到代码预编译和执行两个阶段, 语法分析则在代码块加载完毕时统一检查语法.
上面的就是我们简单的对一段代码进行分析的过程, 下面, 我们讲一下在预编译阶段提到的执行上下文
执行上下文
执行上下文可理解为当前的执行环境, 与该运行环境相对应. 前面我们提到过, JavaScript 中有三种可执行代码块, 当然也对应着三种执行上下文.
全局执行上下文
这是基础上下文, 任何不在函数内部的代码都在全局上下文中. 一个程序中只会有一个全局执行上下文. 它会执行两件事: 创建一个全局的 Windows 对象(浏览器的情况下), 并且设置 this 的值等于这个全局对象.
函数执行上下文
每当一个函数被调用时, 都会为该函数创建一个新的上下文. 每个函数都有它自己的执行上下文, 不过是在函数被调用时创建的. 函数上下文可以有任意多个. 每当一个新的执行上下文被创建.
Eval 执行上下文
执行在 eval 内部的代码也会有它属于自己的执行上下文, 除非你想搞黑魔法, 不然不要轻易使用它.
执行上下文分为两个阶段:
创建阶段
执行阶段
我们主要讨论创建阶段, 执行阶段的主要工作就是分配变量
创建阶段
创建执行上下文的过程中, 主要是做了下面三件事, 如图所示:
1, 确定 this 的值, 也被称为 This Binding.
2,LexicalEnvironment(词法环境) 组件被创建.
3,VariableEnvironment(变量环境) 组件被创建.
This Binding
全局执行上下文中, this 的值指向全局对象, 在浏览器中 this 的值指向 Windows 对象, 而在 Node.JS 中指向这个文件的 module 对象.
函数执行上下文中, this 的值取决于函数的调用方式. 具体有: 默认绑定, 隐式绑定, 显式绑定(硬绑定),new 绑定, 箭头函数, 具体内容请参考 JavaScript 深入之史上最全 --5 种 this 绑定全面解析这篇文章
.
词法环境有两个组成部分
1, 环境记录: 存储变量和函数声明的实际位置
2, 对外部环境的引用: 可以访问其外部词法环境
词法环境有两种类型
1, 全局环境: 是一个没有外部环境的词法环境, 其外部环境引用为 null. 拥有一个全局对象 (Windows 对象) 及其关联的方法和属性 (例如数组方法) 以及任何用户自定义的全局变量, this 的值指向这个全局对象.
2, 函数环境: 用户在函数中定义的变量被存储在环境记录中, 包含了 arguments 对象. 对外部环境的引用可以是全局环境, 也可以是包含内部函数的外部函数环境.
直接看伪代码可能更加直观
- GlobalExectionContext = { // 全局执行上下文
- LexicalEnvironment: { // 词法环境
- EnvironmentRecord: { // 环境记录
- Type: "Object", // 全局环境
- // 标识符绑定在这里
- outer: <null> // 对外部环境的引用
- }
- }
- FunctionExectionContext = { // 函数执行上下文
- LexicalEnvironment: { // 词法环境
- EnvironmentRecord: { // 环境记录
- Type: "Declarative", // 函数环境
- // 标识符绑定在这里 // 对外部环境的引用
- outer: <Global or outer function environment reference>
- }
- }
变量环境
变量环境也是一个词法环境, 因此它具有上面定义的词法环境的所有属性.
在 ES6 中, 词法 环境和 变量 环境的区别在于前者用于存储函数声明和变量 ( let 和 const ) 绑定, 而后者仅用于存储变量 ( var ) 绑定.
使用例子进行介绍
- let a = 20;
- const b = 30;
- var c;
- function multiply(e, f) {
- var g = 20;
- return e * f * g;
- }
- c = multiply(20, 30);
执行上下文如下所示
- GlobalExectionContext = {
- ThisBinding: <Global Object>,
- LexicalEnvironment: {
- EnvironmentRecord: {
- Type: "Object",
- // 标识符绑定在这里
- a: <uninitialized>,
- b: <uninitialized>,
- multiply: <func>
- }
- outer: <null>
- },
- VariableEnvironment: {
- EnvironmentRecord: {
- Type: "Object",
- // 标识符绑定在这里
- c: undefined,
- }
- outer: <null>
- }
- }
- FunctionExectionContext = {
- ThisBinding: <Global Object>,
- LexicalEnvironment: {
- EnvironmentRecord: {
- Type: "Declarative",
- // 标识符绑定在这里
- Arguments: {0: 20, 1: 30, length: 2},
- },
- outer: <GlobalLexicalEnvironment>
- },
- VariableEnvironment: {
- EnvironmentRecord: {
- Type: "Declarative",
- // 标识符绑定在这里
- g: undefined
- },
- outer: <GlobalLexicalEnvironment>
- }
- }
变量提升的原因: 在创建阶段, 函数声明存储在环境中, 而变量会被设置为 undefined(在 var 的情况下)或保持未初始化(在 let 和 const 的情况下). 所以这就是为什么可以在声明之前访问 var 定义的变量(尽管是 undefined ), 但如果在声明之前访问 let 和 const 定义的变量就会提示引用错误的原因. 这就是所谓的变量提升.
参考文档
- https://www.cxymsg.com/guide/mechanism.html#javascript的执行环境
- https://segmentfault.com/a/1190000017812175
- https://juejin.im/post/5dde27615188256ebd1618fb
- https://muyiy.cn/blog/1/1.1.html
来源: http://www.jianshu.com/p/4ed19f40de0a