【JavaScript高级】02-深入JS执行原理:AO、VO及作用域链

深入V8引擎原理

JavaScript代码的执行

JavaScript代码下载好之后,是如何一步步被执行的呢?我们知道,浏览器内核是由两部分组成的,以webkit为例:

  • WebCore:负责HTML解析、布局、渲染等等相关的工作;
  • JavaScriptCore:解析、执行JavaScript代码;
    在这里插入图片描述
    另外一个强大的引擎就是V8引擎

V8引擎的执行原理(了解)

官方对v8引擎的定义

  • V8是用C++编写的Google开源高性能JavaScript和WebAssembly引擎,它用于Chrome和Node.js等。
  • 它实现ECMAScriptWebAssembly,并在Windows7或更高版本,macOS
    10.12+和使用x64,IA-32,ARM或MIPS处理器的Linux系统上运行。
  • V8可以独立运行,也可以嵌入到任何C++应用程序中。
    -在这里插入图片描述

当我们的一段js代码需要进行执行时

  1. V8引擎首先会使用解析器(编译器)(Parse模块)经过词法分析 > 语法分析 这几步生成一颗抽象语法树(AST) ,由于解释器(Ignition模块)是不能直接认识js代码的所以需要经过编译器将js代码生成AST 。当需要进行代码转换时,例如es6,ts代码需要转换成es5的时候,可以访问AST树,将AST树转换为es5规定的代码,然后再生成新的AST树parse的V8官方文档
  2. 抽象语法树在由解释器(Ignition模块)生成字节码(伪汇编代码),生成的字节码可以跨平台使用,在浏览器和node环境下都是可以用字节码直接生成所需要的的结果Ignition的V8官方文档
  3. 字节码接着执行接下来的步骤真正的汇编代码 =>机器码 => cpu执行,这就是当你在调用相关的js函数时,V8引擎需要执行的步骤(上面三步主要由解释型语言实现的)
    在这里插入图片描述4. 如果一个函数被多次调用,那么就会被标记为热点函数,那么就会经过TurboFan转换成优化的机器码,不需要在经过步骤三就可以直接被CPU执行,提高代码的执行性能(这一步主要由C++实现),这里有一个需要注意的是:以上面的代码为例,多次调用sum函数,并且传入的参数类型都是相同类型时(列如number类型),这一步骤才会生效,如果我们这时改变一个参数的类型,之前优化的机器码并不能正确的处理运算,就会逆向的转换成字节码,这样做会让性能大打折扣,如果在使用typescript进行类型检测,是可以提高一定的性能TurboFan的V8官方文档

JavaScript执行过程

初始化全局对象

在这里插入图片描述

执行上下文

什么是执行上下文

  • js引擎内部有一个执行上下文栈(Execution Context Stack,简称ECS),它是用于执行代码的调用栈
  • 当 JS 引擎解析到可执行代码片段(通常是函数调用阶段)的时候,就会先做一些执行前的准备工作,这个 “准备工作”,就叫做 "执行上下文(execution context 简称 EC)" 或者也可以叫做执行环境。

执行上下文的类型

javascript 中有三种执行上下文类型,分别是:

  • 全局执行上下文——这是默认或者说是最基础的执行上下文,一个程序中只会存在一个全局上下文,它在整个 javascript
    脚本的生命周期内都会存在于执行堆栈的最底部不会被栈弹出销毁。全局上下文会生成一个全局对象(以浏览器环境为例,这个全局对象是
    window),并且将 this 值绑定到这个全局对象上。
  • 函数执行上下文——每当一个函数被调用时,都会创建一个新的函数执行上下文(不管这个函数是不是被重复调用的)
  • Eval 函数执行上下文—— 执行在 eval 函数内部的代码也会有它属于自己的执行上下文,但由于并不经常使用
    eval,所以在这里不做分析。

全局代码执行前后的流程

  • 全局的代码块为了执行会构建一个Global Execution Context (GEC) ;
  • GEC会被放入到ECS中执行;

GEC被放入到ECS中里面包含两部分内容:

在这里插入图片描述

VO对象(Variable Object)

每一个执行上下文会关联一个VO (Variable object,变量对象),变量和函数声明会被添加到这个VO对象中。

在这里插入图片描述
全局代码执行过程(执行后)示例

在这里插入图片描述

函数代码执行过程

在执行的过程中执行到一个函数时,就会根据函数体创建一个函数执行上下文(Functional Execution Context,简称FEC)并且压入到EC Stack中。

在这里插入图片描述

作用域链

什么是作用域

简单来说,作用域(英文:scope)是据名称来查找变量的一套规则,可以把作用域通俗理解为一个封闭的空间,这个空间是封闭的,不会对外部产生影响,外部空间不能访问内部空间,但是内部空间可以访问将其包裹在内的外部空间。

[[Scopes]]属性

  • 在javascript中,每个函数都是一个对象,在对象中有些属性我们可以访问,有些我们是不能自由访问的,[[Scopes]]属性就是其中之一,这个属性只能被JavaScript引擎读取。
  • 其实[[scope]]就是我们常说的作用域,其中存储了作用域运行期的上下文集合。
  • 在这里因为func.prototype.constructor和func指向同一个函数,所以在这里我们通过访问函数func的原型对象来查看[[Scopes]]属性
    在这里插入图片描述

作用域链

[[scope]]中存储的执行期的上下文对象的集合,这个集合呈链式连接,我们把这种链式连接叫做作用域链。JavaScript正是通过作用域链来查找变量的,其查找方式是沿着作用域链的顶端依次向下查询(在哪个函数内部查找对象,就在哪个函数作用域链中查找)

变量查找的作用域链顺序

以下面的例子为例,结合着之前讲的变量对象和执行上下文栈,我们来总结一下函数执行上下文中作用域链和变量对象的创建过程:

var Scope = "global Scope";

function checkScope(){

    var Scope2 = 'local Scope';

    return Scope2;

}

checkScope();

执行过程如下:

1.checkScope 函数被创建,保存作用域链到 内部属性[[Scope]]

checkScope.[[Scope]] = [ globalContext.VO];

2.执行 checkScope 函数,创建 checkScope 函数执行上下文,checkScope 函数执行上下文被压入执行上下文栈

ECStack = [

checkScopeContext,

globalContext

];

3.checkScope 函数并不立刻执行,开始做准备工作,第一步:复制函数[[Scope]]属性创建作用域链

checkScopeContext = {

Scope: checkScope.[[Scope]],

}

4.第二步:用 arguments 创建活动对象,随后初始化活动对象,加入形参、函数声明、变量声明

checkScopeContext = {

AO: {

    arguments: {

        length: 0

    },

    Scope2: undefined

},

Scope: checkScope.[[Scope]],

}

5.第三步:将活动对象压入 checkScope 作用域链顶端

checkScopeContext = {

AO: {

    arguments: {

        length: 0

    },

    Scope2: undefined

},

Scope: [AO, [[Scope]]]

}

6.准备工作做完,开始执行函数,随着函数的执行,修改 AO 的属性值

checkScopeContext = {

AO: {

    arguments: {

        length: 0

    },

    Scope2: 'local Scope'

},

Scope: [AO, [[Scope]]]

}

7.查找到 Scope2 的值,返回后函数执行完毕,函数上下文从执行上下文栈中弹出

ECStack = [

globalContext

];

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值