• 【JavaScript高级】JavaScript的运行原理:V8引擎,JS代码执行原理,作用域和作用域链面试题


    V8引擎

    浏览器内核是由两部分组成的,以webkit为例:

    • WebCore:负责HTML解析、布局、渲染等等相关的工作
    • JavaScriptCore:解析、执行JavaScript代码。

    有一个强大的JavaScript引擎就是V8引擎。

    架构

    Parse模块会将JavaScript代码转换成AST(抽象语法树),因为解释器并不直接认识JavaScript代码。如果函数没有被调用,是不会被转成AST的。

    Ignition是一个解释器,会将AST转换成ByteCode(字节码)。同时会收集TurboFan优化所需要的信息(如函数参数的类型信息)。如果函数只调用一次,Ignition会解释执行ByteCode;

    TurboFan是一个编译器,可以将字节码编译为CPU可以直接执行的机器码

    • 如果一个函数被多次调用,那么就会被标记为热点函数,那么就会经过TurboFan转换成优化的机器码,提高代码的执行性能。
    • 机器码也会被还原为ByteCode,因为如果后续执行函数的过程中,类型发生了变化,之前优化的机器码并不能正确的处理运算,就会逆向的转换成字节码

    如:

    function sum(num1,num2){
    	return num1+num2
    }
    '
    运行

    若多次调用sum(10,20),则它会被标记为热点函数,且优化为机器码。这里的数据类型都是Number,执行的操作是加法。
    但这里也可以传入两个字符串,执行的操作是字符串拼接,若如此,之前优化的机器码就要逆向地转换成字节码。

    在这里插入图片描述

    JavaScript代码执行原理

    初始化全局对象

    js引擎会在执行代码之前,会在内存中创建一个全局对象Global Object(GO)

    • 该对象 所有的作用域(scope)都可以访问
    • 里面会包含Date、Array、String、Number、setTimeout、setInterval等
    • 有一个window属性指向自己

    执行上下文 Execution Contexts

    js引擎内部有一个执行上下文栈(Execution Context Stack,简称ECS),它是用于执行代码的调用栈。执行的是全局的代码块:

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

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

    • 代码执行前,在parser转成AST的过程中,会将全局定义的变量、函数等加入到GlobalObject中,但是并不会赋值——这个过程也称之为变量的作用域提升(hoisting)
    • 在代码执行中,对变量赋值,或者执行其他的函数

    VO对象

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

    举个例子:

    var message = "Global Message"
    
    function foo() {
        message = "foo message"
    }
    
    var num1 = 30
    var num2 = 20
    var res = num1 + num2
    console.log(res);
    '
    运行

    这段代码被parse(即转化为AST)时,会有一个VO对象。这段代码里的message、foo、num1、num2、res的声明都会被放到这个VO对象中(注意,函数是提前声明的),但是还没赋值。相当于:

    VO:{
    //创建函数对象,值(键值对的值)是指向函数对象的指针
    	foo:0xa00,
    	message:undefined,	
    	num1:undefined,
    	num2:undefined,
    	res:undefined,
    }
    

    如果执行上下文是全局执行上下文的话,VO就是GO。

    函数的执行

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

    这里的VO是AO(Activation Object),它会使用arguments作为初始化,并且初始值是传入的参数,AO会存放变量的初始化。

    作用域和作用域链的面试题

    看看这个:Js作用域与作用域链详解

    面试题:

    1

    var n = 100
    function foo() {
        n = 200
    }
    foo()
    console.log(n);
    '
    运行

    答:

    200
    '
    运行

    2

    function foo() {
        console.log(n);
        n = 200
        console.log(n);
    }
    var n = 100
    foo()
    '
    运行

    答:

    100
    200
    '
    运行

    3

    var n = 100
    function foo1() {
       console.log(n);
    }
    function foo2() {
       var n = 200
       console.log(n);
       foo1()
    }
    
    foo2()
    console.log(n);
    '
    运行

    答:

    200
    100
    100
    '
    运行

    解析:

    1. foo2里的n是200
    2. 调用foo1,foo1里没有n,所以去它的上层作用域找:它的上层作用域是全局——与调用的位置无关,与定义的位置有关,所以全局里的n是100
    3. 全局里的n是100

    4

    var a = 100
    function foo() {
       console.log(a);
       return
       var a = 100
    }
    
    foo()
    '
    运行

    答:

    undefined
    '
    运行

    解析:foo里有a!代码其实相当于:

    var a = 100
    function foo() {
       var a
       console.log(a);
       //return 后的不会执行
       //return
       //a = 100
    }
    
    foo()
    '
    运行

    所以输出是undefined。

    5

    function foo() {
        var a = b = 100
    }
    foo()
    console.log(a);
    console.log(b);
    

    解析:

    1. 访问不到a,因为a只在foo函数作用域里,在全局里没有
    2. 访问得到b,因为b没有用var声明,于是会被js解析成全局变量,b为100

    参考

    coderwhy的课
    JavaScript代码到底是怎么执行的?
    站在‘上帝 ’角度,透视v8 执行 js 的过程
    Js作用域与作用域链详解

  • 相关阅读:
    缓存策略与Apollo:优化网络请求性能
    error: RPC failed; HTTP 413 curl 22 The requested URL returned error: 413 解决方案
    大数据之Stream流
    SpringCloud中Gateway提示OPTIONS请求跨域问题
    【ROS入门】ROS的核心概念
    js时间转化为几天前,几小时前,几分钟前
    Word文档如何转PDF?这三款软件值得一试
    Docker轻量级可视化工具Portainer
    Unreal&LatentAction的能与不能
    在IIS上部署ASP.NET Core Web API和Blazor Wasm应用程序的完整指南
  • 原文地址:https://blog.csdn.net/karshey/article/details/127030009