“浅谈”JavaScript执行过程

226 篇文章 4 订阅
225 篇文章 3 订阅

"浅谈"JavaScript执行过程

前言:浅谈系列属于个人总结,如果有错误请各位大佬指正

浏览器的工作原理

JavaScript代码,在浏览器是如何被执行的?

当我们在输入服务器地址的时候,例如www.baidu.com,首先会解析 index.html

  • 遇到link标签css文件,就会去服务器下载css文件
  • 遇到script标签,就会去服务器下载JavaScript文件

浏览器渲染过程

当我们解析HTML的时候,遇到了JavaScript会交给谁去处理呢?

认识JavaScript引擎

  • 为什么需要JavaScript引擎呢?

    • 高级的编程语言都是需要转成 最终的机器指令来执行 的
    • 我们编写的JavaScript无论是交给浏览器或者Node执行,最后都需要被CPU执行
    • 需要JavaScript引擎帮助我们将JavaScript代码翻译成CPU指令来执行

V8引擎

  • Pares模块会将JavaScript代码转换成AST(抽象语法树),这是因为解释器并不认识JavaScript代码

    • 如果函数没有被调用,那么是不会被转化成AST的
    const name = "Agility"  // 这里Pares是如何进行分析最终转化成AST呢?

    astexplorer.net/ 这个网站可以看的转化成AST

  • Ignition是一个解释器,会将AST转化成ByteCode(字节码)

    • 同时会收集TurboFan优化所需要的信息(比如函数参数的类型信息,有了类才能进行真实的运算)
    • 如果函数只调用一次,Ignition会执行解释行ByteCode
  • TurboFan是一个编译器,可以将字节码编译为CPU可以直接进行的机器码

    • 如果一个函数被多次调用,那么就会被比标记为 热点函数 ,那么就会经过 TurboFan转换成优化的机器码,提高代码的执行性能
    • 但是,机器码实际上也会被还原为ByteCode,这是因为如果后续执行函数的过程中,类型发生了变化(比如sum函数原来执行的是  number类型,后来执行变成了string类型),之前优化的机器码并不能正确的处理运算,就会逆向的转换成字节码
    // 例如这是一个热函数
    function sum(num1 , num2) {
        num1 + num2
    }
    sum(10,20) // 正常调用直接从TurboFan运行结果
    sum("a" , "b") // string类型,需要优化机器码再运行结果

V8执行的细节

那么我们的JavaScript源码是如何被解析(Parse过程)的呢?

  • 内核(Blink)将源码交给V8引擎,Stream获取到源码并且进行编码转换;

  • Scanner(扫描器)会进行词法分析(lexical analysis),词法分析会将代码转换成tokens;

  • 接下来tokens会被转换成AST树,经过Parser和PreParser:

    • Parser就是直接将tokens转成AST树架构;

    • PreParser称之为预解析,为什么需要预解析呢?

      • 这是因为并不是所有的JavaScript代码,在一开始时就会被执行。那么对所有的JavaScript代码进行解析,必然会  影响网页的运行效率;
      • 所以V8引擎就实现了Lazy Parsing(延迟解析)的方案,它的作用是将不必要的函数进行预解析,也就是只解析暂时需要的内容,而对函数的全量解析是在函数被调用时才会进行;
      • 比如我们在一个函数outer内部定义了另外一个函数inner,那么inner函数就会进行预解析;
  • 生成AST树后,会被Ignition转成字节码(bytecode),之后的过程就是代码的执行过程(后续会详细分析)

// 预解析
function outer() {
    function inner() {
        
    }
}

总结

简单来说,我们的浏览器是可以解析HTML、CSS,但是碰到JavaScript就无法进行解析了,所以v8引擎诞生了,而v8引擎的解析步骤,可以分解为

解析代码:arrow_right:

生成AST树,其中做了优化步骤PreParser(预解析):arrow_right:

转成字节码,再次做优化TurboFan(热函数):arrow_right:

运行结果

JavaScript的执行过程

// 例如
var test = 'Agility'
​
var num1 = 20
var num2 = 30
​
var result = num1 + num2
​
​
/*
    1. 代码被解析v8引擎内部会帮助我们创建一个对象Global Object(GO)
    2. 运行代码
        2.1 v8引擎为了执行代码,v8引擎内部会有一个执行上下文栈(函数调用栈) Execution Context Stack,简称ECS
        2.2 因为我们执行的是全局代码,为了全局代码能够正常的执行,需要创建全局执行上下文
*/
var globalObject = {
    String: "类"
    Data: "类"
    window: globalObject,
    ...
    // 这个时候代码解析,但是没有运行
    test: undefined
    num1: undefined
    num2: undefined
    result: undefined
}

总结

所以就可以解释变量提升的原理,这里不针对函数是如何执行的

  1. 初始化全局对象Global Object(GO)

    • 里面会包含Date、Array、String、Number、setTimeout、setInterval等等;
    • 其中还有一个 window属性指向自己 ;
  2. 执行上下文栈/函数调用栈(Execution Context Stack,简称ECS)

    • 它是用于执行代码的调用栈
  3. 创建全局执行上下文Global Execution Context(GEC)

    • 执行的是全局代码,为了全局代码能够正常的执行,需要创建全局执行上下文
    • 在代码执行前,在parser转成AST的过程中,会将全局定义的变量、函数等加入到GlobalObject中,  但是并不会赋值( 变量的作用域提升 )
    • 在代码执行中,对变量赋值,或者执行其他的函数

JavaScript函数的执行过程

foo() // foo
function foo() {
    console.log('foo')
}

这时候我们发现函数可以正常执行,下面就让我们看看V8引擎在执行函数的过程吧

var test = Agility
​
foo()
​
function foo() {
    console.log(num)
    var num = 123
    console.log('foo')
}

  1. 在执行的过程中执行到一个函数時,会根据函数体创建一个函数执行上下文(Functional Execution Context,  简称FEC),并且压入到ECStack中。----- 放入

    • FEC中包含三部分的内容
    • 第一部分:在解析函数成为AST树结构时,会创建一个Activation Object(AO): ü AO中包含形参、arguments、函数定义和指向函数对象、定义的变量;
    • 第二部分:作用域链:由VO(在函数中就是AO对象)和父级VO组成,查找时会一层层查找;
    • 第三部分:this绑定的值
  2. FEC开始执行 ----- 执行

作用域链

FEC中还包含了作用域链, 父级VO在开始就被确定了 ,看看下面的代码

:arrow_right:当前作用域中存在

var num = 123
​
foo()
​
function foo() {
    var num = 456
    console.log(num) // 456
}

当前作用域中不存在

var num = 123
​
foo() 
​
function foo() {
    console.log(num) // 123
}

可以清楚的看出是如何沿着作用域链寻找

嵌套函数

我们再来看看嵌套函数的执行过程吧

var num = 123
​
foo(456)
function foo(value) {
    console.log(m)
    var m = 10
    var n = 20
    
    function bar() {
        console.log(num)
    }
    
    bar()
}

JavaScript执行过程就聊完了~:grinning:接下来就来看看JavaScript的 内存管理 以及 闭包 的知识吧

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值