深入理解 JavaScript 执行原理与变量提升

1 阅读7分钟

在日常JavaScript开发中,存在一个违背大众直觉的代码运行现象:在变量声明前使用变量不会直接报错,在函数声明前调用函数也可以正常执行。这一现象彻底推翻了“JavaScript代码逐行顺序执行”的基础认知,想要读懂背后的逻辑,就需要深入理解JS的编译执行机制、变量提升、执行上下文等底层核心原理。本文结合核心知识点,纯文字拆解JS完整执行逻辑。

一、打破固有认知:JS并非单纯逐行执行 多数初学者默认JavaScript是逐行解析、逐行执行的脚本语言,但变量和函数的前置使用现象,足以证明这个认知是错误的。 正常逐行执行的逻辑下,未提前定义的变量、未提前声明的函数,在使用时都会直接抛出未定义错误。但实际运行中,变量声明前调用,仅会出现变量值为空的情况,函数声明前调用却可以正常执行。 这核心差异说明:JS代码的运行分为两个阶段,并非只有单纯的执行阶段,在正式执行代码之前,还有一个极其短暂、隐藏的编译阶段,我们熟知的变量提升,就发生在这一阶段。

a19276747bd1b166a2986acd2ead543c.jpg 二、核心概念:什么是变量提升? 很多人会误以为变量提升是JS引擎将变量、函数声明代码物理移动到代码顶部,这是典型误区。 真正的变量提升,是Chrome V8等JS引擎在编译阶段,主动扫描全局或局部作用域内的所有变量声明、函数声明,将其提前载入内存、绑定到对应作用域的行为,代码本身的书写位置不会发生任何改变。 引擎会对不同声明类型做差异化初始化处理,这也是不同前置使用结果的根本原因: 普通var声明的变量,会完成声明提升,并被赋予默认初始值undefined;函数声明作为JS的一等对象,会完整提升整个函数对象,而非仅提升声明,这也是函数可以前置调用、正常执行的核心原因。 而ES6新增的let、const声明变量同样存在提升,但规则完全不同,不会初始化默认值,会形成暂时性死区,在声明前使用会直接触发报错。

8b13225c218713401e561591146d49c3.jpg 三、JS完整运行流程:编译阶段 + 执行阶段 JavaScript属于弱类型动态脚本语言,没有传统编译语言的独立编译打包过程,不会生成中间文件,但会在代码运行前的瞬间完成即时编译,整套运行流程分为两大核心阶段。

  1. 编译阶段:前置准备,搭建运行环境 这一阶段是JS代码能够正常运行的基础,引擎会完成代码扫描、语法校验、声明提升、环境初始化一系列操作,最终生成两大核心内容:可执行代码和执行上下文。 执行上下文是JS代码运行的专属环境,每一段代码、每一个函数的执行,都依赖对应的执行上下文,其中包含变量环境、词法环境等核心模块,所有提升的变量和函数,都会统一挂载到执行上下文中。 其中,变量环境专门存放var声明的变量和普通函数声明,编译阶段扫描到的变量会被初始化为undefined,函数会直接存入完整的函数对象。
  2. 执行阶段:逐行解析运行代码 编译阶段完成所有前置准备后,引擎才会正式逐行执行可执行代码。运行过程中,引擎不会读取原始代码,而是从执行上下文中读取提前挂载好的变量、函数,完成赋值、调用、逻辑运算等所有操作。

1b4b9dae21efc545795e0f43d6e0e9d1.jpg 四、词法环境与暂时性死区 执行上下文包含的词法环境,主要用于存放let、const声明的块级作用域变量,其提升规则和变量环境完全不同。 词法环境中的变量仅会完成声明提升,不会被引擎初始化任何默认值。因此在变量声明之前,该变量处于暂时性死区状态,无法被访问、调用或使用,一旦操作就会直接抛出语法错误,这也是ES6规范为解决var变量提升乱象、优化代码严谨性设计的核心机制。

13e2fa053c6471e9bc02ee2125797cd3.jpg 五、调用栈:管理代码执行顺序 JS是单线程语言,同一时间只能执行一段代码,而调用栈就是负责管理所有执行上下文的核心机制。 全局代码运行时,会创建全局执行上下文并压入调用栈底部;每当调用一个函数,就会创建对应的函数执行上下文并入栈;函数执行完毕后,对应上下文自动出栈销毁;所有代码执行完成后,全局上下文出栈,程序结束。调用栈保证了所有代码上下文有序执行,不发生冲突。

230f7c84b984e3d134b2877ae604e5fe.jpg

六、核心规则总结与原理解析 结合编译和执行两大阶段,就能彻底读懂JS变量使用的三类核心规则,解开所有认知误区: 第一,使用完全未声明、未定义的变量,会直接抛出报错。因为编译阶段引擎扫描不到该变量的任何声明,执行上下文无对应变量,无法识别。 第二,在var变量声明之前使用变量,不会报错,仅返回undefined。因为编译阶段变量已完成提升并初始化默认值,执行阶段可以读取到变量,只是尚未完成自定义赋值。 第三,在函数声明之前调用函数,不会报错且可以正常执行。得益于函数声明的完整提升特性,编译阶段整个函数对象已存入执行上下文,执行阶段可直接调用执行。

七、实战开发优化建议 理解JS执行原理和变量提升,核心目的是规避开发中的隐性bug,优化代码规范性。首先,日常开发优先使用let、const替代var,利用暂时性死区特性,避免变量提升带来的变量值异常、变量污染问题。其次,统一规范代码书写顺序,将函数声明、变量声明写在逻辑调用之前,提升代码可读性和可维护性。最后,理解执行上下文和调用栈的底层逻辑,能够快速排查变量未定义、函数调用异常、作用域混乱等常见问题。

八、全文核心总结

  1. JavaScript代码并非逐行直接执行,核心分为编译预处理和逐行执行两个阶段,变量提升发生在编译阶段。
  2. 变量提升是内存层面的挂载行为,而非代码物理移位,var变量提升初始化undefined,函数声明完整提升,let/const提升无初始化、存在暂时性死区。
  3. 执行上下文是代码运行的核心环境,变量环境、词法环境分别管理不同类型的变量,调用栈统一管理所有执行上下文的执行顺序。 吃透这套底层执行原理,能够从根源上理解JS作用域、变量生命周期、函数执行规则,彻底解决前端基础进阶中的核心难点。