「四」浏览器中js引擎解析过程(看完秒懂!)

我们前几章和讲解了什么浏览器的组成部分以及渲染引擎,今天我们主要讲一下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中有三种可执行代码块,当然也对应着三种执行上下文。

  • 全局执行上下文
    这是基础上下文,任何不在函数内部的代码都在全局上下文中。一个程序中只会有一个全局执行上下文。它会执行两件事:创建一个全局的 window 对象(浏览器的情况下),并且设置 this 的值等于这个全局对象。

  • 函数执行上下文
    每当一个函数被调用时, 都会为该函数创建一个新的上下文。每个函数都有它自己的执行上下文,不过是在函数被调用时创建的。函数上下文可以有任意多个。每当一个新的执行上下文被创建。

  • Eval 执行上下文
    执行在 eval 内部的代码也会有它属于自己的执行上下文,除非你想搞黑魔法,不然不要轻易使用它。

执行上下文分为两个阶段:
  • 创建阶段
  • 执行阶段

我们主要讨论创建阶段,执行阶段的主要工作就是分配变量

创建阶段

创建执行上下文的过程中,主要是做了下面三件事,如图所示:

1、确定 this 的值,也被称为 This Binding。
2、LexicalEnvironment(词法环境) 组件被创建。
3、VariableEnvironment(变量环境) 组件被创建。

This Binding
  • 全局执行上下文中,this 的值指向全局对象,在浏览器中this 的值指向 window对象,而在nodejs中指向这个文件的module对象。

  • 函数执行上下文中,this 的值取决于函数的调用方式。具体有:默认绑定、隐式绑定、显式绑定(硬绑定)、new绑定、箭头函数,具体内容请参考JavaScript深入之史上最全--5种this绑定全面解析这篇文章

词法环境有两个组成部分
  • 1、环境记录:存储变量和函数声明的实际位置

  • 2、对外部环境的引用:可以访问其外部词法环境

词法环境有两种类型

  • 1、全局环境:是一个没有外部环境的词法环境,其外部环境引用为 null。拥有一个全局对象(window 对象)及其关联的方法和属性(例如数组方法)以及任何用户自定义的全局变量,this 的值指向这个全局对象。

  • 2、函数环境:用户在函数中定义的变量被存储在环境记录中,包含了arguments 对象。对外部环境的引用可以是全局环境,也可以是包含内部函数的外部函数环境。

直接看伪代码可能更加直观

GlobalExectionContext = {  // 全局执行上下文
  LexicalEnvironment: {       // 词法环境
    EnvironmentRecord: {        // 环境记录
      Type: "Object",              // 全局环境
      // 标识符绑定在这里 
      outer: <null>                // 对外部环境的引用
  }  
}

FunctionExectionContext = { // 函数执行上下文
  LexicalEnvironment: {       // 词法环境
    EnvironmentRecord: {        // 环境记录
      Type: "Declarative",         // 函数环境
      // 标识符绑定在这里             // 对外部环境的引用
      outer: <Global or outer function environment reference>  
  }  
}

变量环境

变量环境也是一个词法环境,因此它具有上面定义的词法环境的所有属性。
在 ES6 中,词法 环境和 变量 环境的区别在于前者用于存储函数声明和变量( letconst绑定,而后者仅用于存储变量( 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 的情况下)或保持未初始化(在 letconst 的情况下)。所以这就是为什么可以在声明之前访问 var 定义的变量(尽管是 undefined ),但如果在声明之前访问 letconst 定义的变量就会提示引用错误的原因。这就是所谓的变量提升。

执行上下文的属性

对于每个执行上下文,都有三个重要属性:

  • 变量对象(Variable object,VO)
  • 作用域链(Scope chain)
  • this

下面我们逐一简略讲解下:

变量对象

变量对象是与执行上下文相关的数据作用域,存储了在上下文中定义的变量和函数声明。
不同执行上下文下的变量对象稍有不同
全局上下文中的变量对象是全局对象
函数上下文:
在函数上下文中,我们用活动对象(activation object, AO)来表示变量对象。
活动对象和变量对象其实是一个东西,但是两者的区别在于

  • 1、变量对象(VO)是规范上或者是JS引擎上实现的,并不能在JS环境中直接访问。
  • 2、当进入到一个执行上下文后,这个变量对象才会被激活,所以叫活动对象(AO),这时候活动对象上的各种属性才能被访问。

活动对象是在进入函数上下文时被创建的,它通过函数的 arguments 属性初始化。arguments 属性值是 Arguments 对象。

函数上下文的执行过程

执行上下文的代码会分成两个阶段进行处理:分析和执行,我们也可以叫做:

  • 进入执行上下文
  • 代码执行
进入执行上下文

当进入执行上下文时,这时候还没有执行代码,变量对象会包括:

  • 1、函数的所有形参 (如果是函数上下文):由名称和对应值组成的一个变量对象的属性被创建,如果没有实参,属性值设为undefined
  • 2、函数声明:由名称和对应值(函数对象(function-object))组成一个变量对象的属性被创建,如果变量对象已经存在相同名称的属性,则完全替换这个属性
  • 3、变量声明:由名称和对应值(undefined)组成一个变量对象的属性被创建,如果变量名称跟已经声明的形参或函数相同,则变量声明不会干扰已经存在的这类属性
代码执行

在代码执行阶段,会顺序执行代码,根据代码,修改变量对象的值
举个例子:

function foo(a) {
  var b = 2;
  function c() {}
  var d = function() {};
  b = 3;
}
foo(1);

在进入执行上下文后,这时候的 AO 是:

AO = {
    arguments: {
        0: 1,
        length: 1
    },
    a: 1,
    b: undefined,
    c: reference to function c(){},
    d: undefined
}

代码执行的时候变成:

AO = {
    arguments: {
        0: 1,
        length: 1
    },
    a: 1,
    b: 3,
    c: reference to function c(){},
    d: reference to FunctionExpression "d"
}

下面思考这两道题目:输出的会是什么?

var foo = function () {
    console.log('foo1');
}
foo();  // foo1
var foo = function () {
    console.log('foo2');
}
foo(); // foo2

然而去看这段代码:

function foo() {
    console.log('foo1');
}
foo();  // foo2
function foo() {
    console.log('foo2');
}
foo(); // foo2

这是因为在进入执行上下文时,首先会处理函数声明,其次会处理变量声明,如果如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性。

我们上文讲了每个执行上下文都有三个重要的属性,变量对象,作用域和this,由于作用域和this的篇幅过于长,所以,暂时不在这篇文章做展示

参考文档

https://www.cxymsg.com/guide/mechanism.html#javascript%E7%9A%84%E6%89%A7%E8%A1%8C%E7%8E%AF%E5%A2%83
https://segmentfault.com/a/1190000017812175
https://juejin.im/post/5dde27615188256ebd1618fb
https://muyiy.cn/blog/1/1.1.html
https://github.com/mqyqingfeng/Blog/issues/5

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,835评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,598评论 1 295
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,569评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,159评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,533评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,710评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,923评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,674评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,421评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,622评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,115评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,428评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,114评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,097评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,875评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,753评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,649评论 2 271

推荐阅读更多精彩内容