深入理解 JavaScript 变量与函数声明提升机制
本文深入解析 JavaScript 中的声明提升(Hoisting)机制,涵盖 var、let、const 及函数声明在编译阶段的行为差异。通过具体代码示例,展示变量与函数在作用域内的提升顺序及覆盖规则,并结合 ES6 新特性与 ESLint 规范,提供避免提升陷阱的最佳实践方案。
前言:
JavaScript 中的变量与函数声明提升(Hoisting)机制,本质上是执行上下文处理流程中的一种预编译行为。从字面含义理解,“提升”并非指变量或函数在源代码中的物理位置发生了移动,而是指在代码编译阶段,它们的声明会被提取并放入内存中,导致其实际可用顺序与代码书写顺序不一致。尽管在现代前端开发中,这种机制对日常编码的影响已逐渐减小,尤其是随着 ES6 标准的广泛普及,但它仍是前端开发的基础核心知识,也是各大互联网公司前端面试的高频考点。此处分享的内容并非新颖理论,而是作为个人学习笔记,旨在加速对这一概念的深入理解。
在 ES5 标准中,变量提升主要体现为 声明和函数声明的行为;而在 ES6 标准中, 和 则不具备变量提升的特性。
变量提升
JavaScript 引擎的执行流程是:先解析代码,收集所有声明的变量和函数,然后再逐行执行代码。这一机制导致所有变量的声明语句在逻辑上会被移动到代码块的顶部,这种现象被称为变量提升(Hoisting)。
此处所指的变量声明,明确包含函数声明。接下来通过具体代码进行演示:
function hoistingVariable() {
if (!devpoint) {
var devpoint = 1;
}
console.log(devpoint);
}
hoistingVariable();
// 下面是输出结果
// 1
当变量处于函数作用域内时,解析器会查找该作用域中的声明。如 devpoint 在 if 语句块中虽未显式声明,但根据变量提升规则,该变量的声明会被提升至函数体的第一行,而其赋值操作仍保留在原位。
上述代码的实际执行效果等同于以下重写后的代码:
function hoistingVariable() {
var devpoint;
if (!devpoint) {
devpoint = 1;
}
console.log(devpoint);
}
hoistingVariable();
接下来增加一些具有迷惑性的代码示例,如下所示:
var devpoint = "out";
function hoistingVariable() {
var devpoint;
if (!devpoint) {
devpoint = "in";
}
console.log(devpoint);
}
hoistingVariable();
console.log(devpoint);
// 下面是输出结果
// in
// out
对于同名变量的声明,个人理解是遵循作用域查找的就近原则。如果函数体内存在 声明,则仅在函数内部进行查找,不受外部作用域中同名声明的影响。
若将上述函数体内的声明语句移除,输出结果将会发生变化。
var devpoint = "out";
function hoistingVariable() {
if (!devpoint) {
devpoint = "in";
}
console.log(devpoint);
}
hoistingVariable();
console.log(devpoint);
// 下面是输出结果
// out
// out
当函数体内没有声明语句时,解析器会向外层作用域查找声明。基于此规则,由于外部已对变量进行声明并赋值,函数体内的 if 判断条件将不再成立,从而不会执行内部赋值逻辑。
下面代码调整了赋值的顺序,具体代码如下:
var devpoint;
function hoistingVariable() {
if (!devpoint) {
devpoint = "in";
}
console.log(devpoint);
}
devpoint = "out";
hoistingVariable();
console.log(devpoint);
// 下面是输出结果
// out
// out
根据前述原理,函数体内的变量实际上是由外部声明的,但尚未赋值。函数本身虽然被提升,但在执行前并未完成赋值。在函数执行前,将 devpoint 赋值为外部值,再执行函数时,结果便变为 out。
函数提升
前文已介绍变量提升同样涵盖函数声明。不同形式的函数声明,其提升后的执行行为存在差异。以下直接通过代码进行说明。
function hoistingFun() {
hello();
function hello() {
console.log("hello");
}
}
hoistingFun();
// 下面是输出结果
// hello
上述代码能够正常运行,原因在于函数声明被提升。函数 hello 被提升至作用域顶部,其运行效果与以下代码完全一致:
function hoistingFun() {
function hello() {
console.log("hello");
}
hello();
}
hoistingFun();
// 下面是输出结果
// hello
如果在同一作用域内对同一个函数进行多次声明,后续的函数声明会覆盖前面的声明。
function hoistingFun() {
hello();
function hello() {
console.log("hello");
}
function hello() {
console.log("hello2");
}
}
hoistingFun();
// 下面是输出结果
// hello2
两个函数声明均被提升,按照声明的先后顺序,后面的声明会覆盖前面的声明结果。
函数声明常见的方式有两种,另一种是匿名函数表达式声明方式。这种方式可视为变量声明来处理。当作用域中同时存在函数声明和变量声明时,函数声明的优先级最高。将上述代码更改后,结果会有所不同:
具体示例如下:
function hoistingFun() {
hello();
function hello() {
console.log("hello");
}
var hello = function () {
console.log("hello2");
};
}
hoistingFun();
// 下面是输出结果
// hello
上述代码的编译逻辑如下:
function hoistingFun() {
function hello() {
console.log("hello");
}
hello();
hello = function () {
console.log("hello2");
};
}
hoistingFun();
// 下面是输出结果
// hello
接下来再看一个示例:外部使用变量声明,而函数体内使用函数声明的情况。
var hello = 520;
function hoistingFun() {
console.log(hello);
hello = 521;
console.log(hello);
function hello() {
console.log("hello");
}
}
hoistingFun();
console.log(hello);
// 下面是输出结果
// [Function: hello]
// 521
// 520
前文已提及,在函数体内声明的变量或函数,其作用范围仅限于函数内部,受限于该作用域,不受外部声明的影响,这相当于函数体内作用域与外部形成了隔离。
上述代码编译后的逻辑如下:
var hello = 520;
function hoistingFun() {
function hello() {
console.log("hello");
}
console.log(hello);
hello = 521;
console.log(hello);
}
hoistingFun();
console.log(hello);
在变量声明中,函数声明的优先级最高,永远会被提升到作用域的最顶部,随后才是函数表达式和变量的执行顺序。
来看下面的代码:
var hello = 520;
function hello() {
console.log("hello");
}
console.log(hello);
// 下面是输出结果
// 520
根据函数声明优先级最高的原则,上述代码的执行逻辑如下:
function hello() {
console.log("hello");
}
hello = 520;
console.log(hello);
为什么要提升?
关于提升机制的设计初衷,此处不作深入展开,仅提供一些参考文章,有兴趣的读者可以查阅。
- 《 Note 4. Two words about “hoisting”》
- 《浅析JavaScript中的变量提升》
最佳实践
在现代 JavaScript 开发中,已有多种方法可以避免变量提升带来的潜在问题。推荐使用 let、const 替代 var,并使用 eslint 等工具来避免变量的重复定义。在一些前端开发团队中,可以针对团队制定规范化的脚手架,例如在项目初始化时强制规定目录结构、配置 ESLint 最佳实践等。通过程序化的规范流程来约束代码,比人工督促更加可靠。
通过下面的代码可以直观看到 和 声明变量的区别。 声明的变量不会发生提升,具体差异可查阅《javascript 变量声明 var,let,const 的区别》。
console.log("1a", myTitle1);
if (1) {
console.log("1b", myTitle1);
var myTitle1 = "devpoint";
}
if (1) { // 这里的代码是有错误无法执行
console.log("3c", myTitle2);
const myTitle2 = "devpoint";
}
// 下面是输出结果
// 1a undefined
// 1b undefined
总结
通过自我学习变量与函数提升机制,加深了对这一概念的理解。对于前端面试中涉及的类似问题,现在可以自信地给出准确答案,这算是一种收获。
到此这篇关于 Javascript 变量函数声明提升加深理解的文章就介绍到这了,更多相关 JS 变量声明内容请搜索本站以前的文章或继续浏览下面的相关文章,希望大家以后多多支持本站!
JS处理含Emoji或中文的字符串转数组乱码问题
2026-11-05 16:12:28
JS怎么使用展开运算符合并对象
2026-11-05 14:42:18
ES6+环境下判断JS对象为空的简洁写法
2026-11-05 12:12:23
怎么通过replacer函数自定义JS中JSON的格式化输出
2026-11-05 11:19:44
JS正则表达式基础:字面量与RegExp构造函数的区别及常用元字符
2026-11-05 11:16:58
