当你输入 var 时,浏览器引擎做了什么?
核心目标
深入理解在 JavaScript 代码中遇到 var variableName; 或 var variableName = value; 这类声明语句时,浏览器内部的 JavaScript 引擎(如 V8) 所执行的关键步骤及其背后的原理。
核心概念梳理 JavaScript 执行阶段概述:
代码不是逐行立即执行的! 引擎在执行前会进行准备工作。
两个主要阶段:
编译/解析/准备阶段:
引擎扫描(解析)整个代码文件。
查找 var、function(旧式声明)等声明语句。
主要任务:收集所有变量和函数的声明,为它们“预留位置”(内存管理基础)。
执行阶段:
逐行执行代码(赋值、计算、函数调用等)。
在声明语句的位置,对预存的变量进行初始化或赋值。 var 声明的特殊性:变量提升
概念: var 声明的变量名会在其实际书写位置所在的整个作用域(通常是全局或函数作用域)的顶部被“提升”或“暴露”出来。
本质: 发生在 编译/准备阶段。引擎把扫描到的 var 声明的变量名记录到了当前作用域对应的 “变量环境” 中,并初始化为一个特殊值 undefined。
结果: 在作用域内任何地方(包括声明语句之前)都可以“看到”这个变量名,只不过在声明语句之前访问它的值是 undefined。
为什么? 这是早期 JavaScript 语言规范(ECMAScript)的设计。
引擎内部处理 var 的详细步骤流程 编译/准备阶段:
词法分析 & 语法分析: 引擎读取源码 var x = 10;,识别出这是一个变量声明语句。
作用域确定: 引擎检查该 var 语句所处的代码块位置,确定其作用域(函数作用域或全局作用域)。
变量环境填充:
在识别的作用域对应的 “变量环境” 对象(可以想象为一个 JS 对象)中添加一个属性。
属性名 (Key): 声明的变量名(如 x)。
属性值 (Value): 被 初始化为 undefined。
注意:此时只看声明 var x; 部分,= 10 是赋值,在这个阶段 不 被执行。 执行阶段:
引擎开始逐行执行代码。
当执行流到达 var x = 10; 这一行时:
变量查找: 引擎首先在当前作用域的变量环境中查找变量 x。
查找结果: 引擎找到 x(因为它在准备阶段已被添加,值为 undefined)。
赋值操作: 引擎将数值 10 赋值给变量环境中的 x(此时 x 的值从 undefined 变成了 10)。
执行到作用域内其他位置访问 x 时:
引擎同样在当前作用域变量环境中查找 x 并返回其当前值(可能是 undefined 或已赋值的值)。
📌 关键演示案例 (Demo)
console.log(y); // 输出什么? var y = 20; console.log(y); // 输出什么?
🔍 引擎执行过程解析 准备阶段:
扫描整个代码。
发现 var y = 20;。
在全局作用域的变量环境中添加属性 y,值为 undefined。 执行阶段:
执行 console.log(y);
引擎查找 y ⇒ 在变量环境中找到,值是 undefined。
输出:undefined
执行 var y = 20;
var y:编译阶段已处理过,执行阶段不再处理声明部分。
= 20:这是赋值操作。引擎查找变量环境中的 y,将其值设置为 20。
执行 console.log(y);
引擎查找 y ⇒ 在变量环境中找到,值是 20。
输出:20
重要对比(与 let/const) 特性 var let / const 作用域 函数作用域 或 全局作用域 块级作用域 ({}) 变量提升 有 (初始化为 undefined) 存在暂存死区 (TDZ) 现象 (声明前访问报错) 重复声明 允许 (无错误) 不允许 (报错 SyntaxError) 全局对象属性 在全局作用域声明会成为 window/global 的属性 不会 成为全局对象属性
📌 let/const 演示案例
console.log(z); // 报错:ReferenceError: Cannot access ‘z’ before initialization let z = 30; console.log(z);
引擎准备阶段也会扫描 let z = 30; 并登记 z,但不会初始化它为 undefined,而是将其置于 “暂存死区”。在声明语句 let z… 之前访问 z 会触发错误。
✅ 关键要点总结 核心机制:分阶段执行。
准备/编译阶段: var 声明的变量名在其作用域顶部被提升,并初始化为 undefined(只处理声明本身)。
执行阶段: 在代码执行到 var 语句所在行时,完成赋值操作(如果存在初始化值);在之前访问则得到 undefined。 作用域影响: var 的作用域是函数级或全局级,不是块级({})。在 if/for 等块语句中用 var 声明的变量会暴露到其所在的函数或全局作用域中。
与 let/const 本质区别:
var 的提升伴随着初始化 (undefined),导致声明前可访问(尽管值是 undefined)。
let/const 虽然其声明也会在编译阶段被处理(登记标识符),但在执行到声明语句前访问会触发 TDZ(暂存死区)错误,提供了更强的安全性和可预测性。它们遵循块级作用域且不允许重复声明。 实践建议: 在现代 JavaScript 开发中,强烈推荐优先使用 let 和 const 代替 var,它们提供的块级作用域和清晰的声明时机(无提升带来的副作用)能显著减少代码错误和理解难度。仅在特定场景(如需要明确函数作用域变量或兼容极老环境)时考虑 var。
这份笔记涵盖了用户主题的核心要点:var 声明的处理流程、变量提升的本质、引擎内部执行阶段的分工、关键性案例演示以及与 let/const 的对比总结,并用通俗语言解释了底层机制。