【V8引擎】JavaScript变量提升

发布时间:2024年01月18日

重学JavaScript02----- 深入JavaScript运行原理



前言

JavaScript使用var关键字声明变量为什么会存在变量提升?
JavaScript函数为什么可以提前调用?

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

我们可以通过了解V8引擎的运行机制了解JS代码在浏览器中的编译过程,来解释为什么存在声明提升的情况

V8引擎的解析图(官方)

img

V8执行的细节

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

  1. Blink将源码交给V8引擎,Stream获取到源码并且进行编码转换;

  2. Scanner会进行词法分析(lexical analysis),词法分析会将代码转换成tokens;

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

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

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

重点:预解析是导致JavaScript代码变量提升的关键

这是因为并不是所有的JavaScript代码,在一开始时就会被执行。那么对所有的JavaScript代码进行解析,必然会影响网页的运行效率;

所以V8引擎就实现了Lazy Parsing(延迟解析)的方案,它的作用是将不必要的函数进行预解析,也就是只解析暂
时需要的内容
,而对函数的全量解析是在函数被调用时才会进行;

比如我们在一个函数outer内部定义了另外一个函数inner,那么inner函数就会进行预解析;

请添加图片描述

它不会解析inner中的代码;只有在调用inner后才会解析。以此提高浏览器性能;

生成AST树后,会被Ignition转成字节码(bytecode),之后的过程就是代码的执行过程(后续会详细分析)

初始化全局对象

js引擎会在执行代码之前,会在堆内存中创建一个全局对象:Global Object(GO)
该对象 所有的作用域(scope)都可以访问;
里面会包含DateArrayStringNumbersetTimeoutsetInterval等等;
其中还有一个window属性指向自己;

node环境: 是global而不是window

执行上下文栈(调用栈)

js引擎内部有一个执行上下文栈(Execution Context Stack,简称ECS),它是用于执行代码的调用栈
那么现在它要执行谁呢?执行的是全局的代码块

  1. 全局的代码块为了执行会构建一个 Global Execution Context(GEC)
  2. GEC会 被放入到ECS中 执行;
    GEC被放入到ECS中里面包含两部分内容:
    第一部分:在代码执行前,在parser转成AST的过程中,会将全局定义的变量、函数等加入到GlobalObject中,
    但是并不会赋值
    这个过程也称之为变量的作用域提升hoisting
    第二部分:在代码执行中,对变量赋值,或者执行其他的函数;

JavaScript的执行过程

变量提升的原因

假如我们有下面一段代码,它在JavaScript中是如何被执行的呢?
请添加图片描述

执行过程1:预解析

请添加图片描述

执行过程2: 开始执行依次往下执行代码
第一行的name变成 => ‘八嘎’
第三行的foo函数内的name因为没调用 name =‘undefined’
第八行打印GO预解析中的num1 :undefined
第十四行调用foo函数 往下执行name从undefined 被覆盖为‘压路’
第五行打印‘压路
第十五行打印1346

打印num

函数如何执行?

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

FEC中包含三部分内容:

  1. 在解析函数成为AST树结构时,会创建一个Activation Object(AO):
    AO中包含形参、arguments、函数定义和指向函数对象、定义的变量;
  2. 作用域链:由VO(在函数中就是AO对象)和父级VO组成,查找时会一层层查找;
  3. this绑定的值:根据不同情况绑定this;
function foo() {
	console.log(n)
	var n = 200
	console.log(n)
}

var n = 100
foo()

执行过程:预解析

请添加图片描述

直到foo()调用函数
根据函数体创建一个函数执行上下文,压入ECS

请添加图片描述

继续从上往下执行代码
我们打印第一个console.log(n)时会发现是n 是 undefined
因为在解析函数成为AST树结构时,会创建一个Activation Object(AO),而AO中不会管代码怎么上下执行,foo函数内var定义的a直接被定义为了undefined,根据作用域链的规则,我们会首先打印内部的n,所以打印才是undefined,而不是100
继续执行下一句代码 var n=200 AO中的 n:undefined 被覆盖为200
打印 n 既得 200

文章来源:https://blog.csdn.net/m0_68324632/article/details/127069300
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。