JavaScript 中的变量提升、执行流程和执行上下文

一、变量提升

js 复制代码
// 变量声明与赋值 
var myname = '竹合'; // 这个代码可以看成是两部分 
// 声明 var myname; 赋值 myname = '竹合' 

// 函数声明
function foo() { 
    console.log('foo'); 
} // 直接声明函数 

var bar = function() { 
    console.log('bar'); 
} // 先声名变量var 再把函数赋值给变量bar

所谓的变量提升 ,是指JS代码执行过程中,JS引擎把变量的声明部分和函数的声明部分 提升到代码开头的行为,变量提升后,会给变量设置默认值undefined

也就是函数和变量执行之前都提升到了代码开头(执行上下文开头)

1.var声明的变量

js 复制代码
console.log(x); // undefined
var x = 5;
console.log(x); // 5

尽管 console.log(x) 出现在 var x = 5; 语句之前,但不会引发错误。在编译阶段,var x; 的声明被提升到顶部,因此第一个 console.log(x) 输出 undefined。 只有声明会被提升,而初始化不会。因此,变量 x 的值是在执行到 var x = 5; 时赋值的,第二个 console.log(x) 输出 5

2. 函数声明,整个函数都会被提升:

js 复制代码
hello(); // "Hello, world!"
function hello() {
  console.log("Hello, world!");
}

hello 函数在其声明之前被调用,这是因为整个函数声明都被提升到其所在作用域的顶部。

3. letconst 声明的变量不会被提升

js 复制代码
console.log(y); // ReferenceError: y is not defined
let y = 10;
console.log(y); // 10

原因:

  1. Temporal Dead Zone (TDZ-暂时性死区): 使用 letconst 声明的变量存在一个叫做 "Temporal Dead Zone"(时态死区)的概念。在声明变量之前的代码区域称为时态死区,在这个区域内访问变量会导致 ReferenceError
  2. Block Scoping(块级作用域): 使用 letconst 声明的变量具有块级作用域,而不是函数级作用域。这与 var 不同,它具有函数级作用域。
js 复制代码
if (true) {
  console.log(y); // ReferenceError: Cannot access 'y' before initialization
  let y = 10;
}

y 的作用域仅限于 if 语句块内,而不是整个函数或全局作用域。在声明之前访问 y 会触发时态死区。

由于这些规则,JavaScript 引擎不会将 letconst 声明的变量提升到作用域的顶部。变量只有在代码执行到达声明语句时才会被初始化,因此在声明之前的代码中访问这些变量会导致时态死区错误。这样的设计使得 JavaScript 更具可预测性和安全性。

实际上变量和函数声明在代码里的位置是不会改变的,而且在编译阶段被JS引擎放入在内存中。 在JS的内存机制种讲解变量函数如何存储的。(暂时还没写 后续补上)

二、JS代码的执行流程

  1. 解析(Parsing):

JavaScript 引擎首先会对代码进行解析,将源代码转换为抽象语法树(Abstract Syntax Tree,AST)。

  1. 编译(Compilation):

解析后,引擎会对生成的 AST 进行编译,将其转换为可执行的字节码或机器代码。有些 JavaScript 引擎(比如 V8)会使用即时编译(Just-In-Time Compilation,JIT Compilation)将字节码直接转换为机器代码。

在这里得到了AST和执行上下文后,解释器就会根据AST生成字节码,并解释执行字节码。这里可能会有疑问,不是应该转为执行效率更高的机器码吗。一开始V8是把AST直接转为机器码的,让它获得了显著的性能提升;但随着移动设备的普及,特别是在手机端,内存空间有限,我们知道机器码其实就是一堆二进制数据,很占空间,所以导致V8的这种方式,极大的消耗内存。后来为了解决这个问题,V8引入了字节码。因为字节码体积比机器码的小得多,减少了系统内存的占用。

  1. 执行(Execution):

有了字节码后,接下来解释器就开始解释执行字节码了。在解释器执行字节码的过程中,如果发现有热点代码,就是某段代码被重复执行了多次,就称为热点代码,那么编译器就会把该段热点的字节码编译为高效的机器码,然后当再次执行这段被优化的代码时,只需要执行编译后的机器码就可以了,这样就大大提升了代码的执行效率。

代码开始执行。执行过程中,JavaScript 引擎按照特定的规则和步骤逐行执行代码。

在 V8 引擎中,Y编译器指的是 TurboFan,它是 V8 引擎的即时编译器(JIT Compiler)。TurboFan 负责将 JavaScript 代码转换为高效的本地机器代码。

V8 引擎中的编译器包括两个主要组件:

  1. Crankshaft(爆震短程): Crankshaft 是 V8 引擎早期版本中的编译器,负责将 JavaScript 代码编译成优化的中间表示(Intermediate Representation,IR)。然后,它通过优化和生成本地机器代码来执行。
  2. TurboFan(涡轮扇): TurboFan 是 V8 引擎的后续版本中引入的编译器。它取代了 Crankshaft,并提供了更先进的优化技术。TurboFan 不仅负责生成本地机器代码,还能够执行更高级的优化,包括类型推断、内联缓存、多层次优化等。

TurboFan 使用了一种称为"Sea of Nodes"的表示形式,这是一种图形表示法,用于表示中间表示中的操作和数据流。这种表示形式使得 TurboFan 能够进行更灵活、更智能的优化。

三、执行上下文

执行上下文(Execution Context)是 JavaScript 中管理代码执行的环境的抽象概念。每当 JavaScript 代码在运行时,都会创建一个执行上下文,用于管理变量、函数声明、this指向等信息。

每个执行上下文都有自己的变量对象(Variable Object)、作用域链(Scope Chain)、this 指向,以及一些其他信息。执行上下文可以分为三种类型:

1. 全局执行上下文(Global Execution Context):

是默认的、最外层的执行上下文。它在整个 JavaScript 程序的生命周期中一直存在,包含了全局变量和函数。

2. 函数执行上下文(Function Execution Context):

每当调用一个函数时,都会创建一个新的函数执行上下文。每个函数都有自己的执行上下文,包含了在函数内声明的局部变量和函数参数。

3. eval 函数执行上下文:

使用 eval 函数时创建的执行上下文,但由于不推荐使用 eval,不做详细讨论,后面有说(this讲解中,还没写)。

执行上下文的生命周期包括两个阶段:

  • 创建阶段(Creation Phase):

在这个阶段,JavaScript引擎会创建变量对象、建立作用域链、确定 this 的值,并进行其他一些初始化工作。

  • 代码执行阶段(Code Execution Phase):

在这个阶段,JavaScript 引擎会按照代码的顺序执行具体的语句,给变量赋值,执行函数等。

执行上下文的栈(Execution Context Stack)是一个栈数据结构,用于管理执行上下文的顺序。当执行一个函数时,会将该函数的执行上下文推入栈中,函数执行完毕后,其执行上下文会从栈中弹出,控制权交给下一个执行上下文。

总的来说,执行上下文是 JavaScript 运行时环境的一个关键概念,它确保了代码的正确执行并提供了作用域、变量和 this 的正确解析。

  1. 在JS执行全局代码的时候,会编译全局代码并创建全局执行上下文,而且在整个页面的生命周期内,全局执行上下文只有一份

  2. 当调用一个函数的时候,函数体内的代码会被编译,并创建函数执行上下文,一般情况下,函数执行结束后,创建的函数执行上下文会被销毁。

  3. 当使用eval函数的时候,eval的代码也会被编译,并创建执行上下文。

相关推荐
大雄野比2 分钟前
Vue3.5常用特性整理
前端·javascript·vue.js
m0_528723813 分钟前
如何利用i18n实现国际化
前端·javascript·vue.js
码农研究僧3 分钟前
Vue 3 中的 el-tooltip 详解:语法、示例及与其他框架对比
javascript·vue.js·elementui·el-tooltip
一个处女座的程序猿O(∩_∩)O17 分钟前
React+AI 技术栈(2025 版)
前端·人工智能·react.js
恰小面包18 分钟前
react使用DatePicker日期选择器
前端·react.js·前端框架
engchina18 分钟前
React中key值的正确使用指南:为什么需要它以及如何选择
前端·react.js·前端框架
engchina19 分钟前
React组件开发技巧:如何优雅地传递Props?
前端·javascript·react.js
偷光25 分钟前
Vue3 对比 React18—不只是技术选择
前端·javascript·react.js
魔众27 分钟前
FocusAny v0.6.0 MacOS和Linux安装优化,独立窗口显示优化
javascript·开源·编辑器
Lsland..1 小时前
Spring Boot 配置文件详解:YAML vs Properties
java·前端·spring boot