CSC-NOTESCSC-NOTES
首页
  • 前端笔记

    • Vue
    • React
    • CSS
    • JavaScript
    • TypeScript
  • 服务端笔记

    • NodeJS
  • C/C++

    • C语言
  • 数据库笔记

    • Postgresql
  • 计算机网络

    • 计算机网络基础
    • 计算机网络进阶
  • 操作系统

    • 操作系统基础
    • 操作系统进阶
  • 前端工程化

    • Vue
    • React
  • 后端工程化

    • NodeJS
  • 数据结构
  • 算法
  • 前端面试
  • 后端面试
Github
首页
  • 前端笔记

    • Vue
    • React
    • CSS
    • JavaScript
    • TypeScript
  • 服务端笔记

    • NodeJS
  • C/C++

    • C语言
  • 数据库笔记

    • Postgresql
  • 计算机网络

    • 计算机网络基础
    • 计算机网络进阶
  • 操作系统

    • 操作系统基础
    • 操作系统进阶
  • 前端工程化

    • Vue
    • React
  • 后端工程化

    • NodeJS
  • 数据结构
  • 算法
  • 前端面试
  • 后端面试
Github
  • JavaScript
    • JavaScript基础
      • 1、语法基础
        • 1.1语法
        • 1.2变量
        • 1.3数据类型
        • 1.4操作符
        • 1.5语句
    • JavaScript高级
      • this指向问题
      • 作用域和闭包
      • 变量和函数提升

本文将帮助你深入理解JavaScript中的变量和函数提升机制,掌握代码执行顺序的底层原理,避免开发中的常见陷阱。

1. 变量提升现象解析

1.1 代码执行顺序的误解

直觉上认为JavaScript代码在执行时是由上到下一行一行执行的。但实际上这并不完全正确。让我们通过几个实际的例子来理解这个特性。

1.2 变量声明后置的情况

思考如下代码:

a = 2;
var a;
console.log(a); // 这里会打印什么呢?

初学者可能会认为输出是undefined,这是基于以下推理:

  1. var a声明在a = 2之后
  2. 变量被重新声明,应该被赋予默认值undefined

但实际上,这段代码会输出2。这个结果暗示了JavaScript中一个重要的机制:变量提升。

1.3 变量使用前声明的情况

考虑另一个更具有迷惑性的例子:

console.log(a);
var a = 2;

这段代码会输出什么?有两种常见的猜测:

  1. 输出2(基于第一个例子的经验)
  2. 抛出错误(因为变量在使用前没有声明)

然而,实际结果是:输出undefined。这个看似矛盾的结果,正是我们需要深入理解JavaScript变量提升机制的原因。

2. JavaScript提升机制的工作原理

2.1 编译阶段的声明处理

JavaScript代码的执行分为两个阶段:编译阶段和执行阶段。在编译阶段,JavaScript引擎会:

  1. 扫描所有的代码
  2. 找到所有的变量和函数声明
  3. 将这些声明与它们各自的作用域关联起来

这个过程是词法作用域规则的具体实现,也是变量和函数提升现象的根本原因。

2.2 声明和赋值的分离处理

一个关键的概念是:JavaScript引擎会将变量声明和赋值操作分开处理。

以var a = 2;为例,JavaScript引擎会将其解析为两个独立的操作:

  1. var a; - 变量声明(在编译阶段处理)
  2. a = 2; - 变量赋值(在执行阶段处理)

2.3 代码处理的实际过程

让我们通过具体例子来理解这个过程:

示例1:声明后赋值

var a;
a = 2;
console.log(a); // 2

这段代码清晰地展示了声明和赋值的分离:

  • 编译阶段:处理var a;
  • 执行阶段:处理a = 2;和console.log(a);

示例2:声明和赋值的提升效果

var a;
console.log(a); // undefined
a = 2;

这个例子展示了为什么提升看起来像是将声明"移动"到了代码的最前面:

  • 声明var a在编译阶段就被处理了
  • 而赋值操作a = 2保持在原来的位置
  • 因此console.log(a)时只能得到undefined注意:只有声明本身会被提升,而赋值或其他运行逻辑会留在原地。如果提升改变了代码的执行顺序,那会造成代码运行的混乱。
foo();
function foo() {
	console.log(a); // undefined
	var a = 2;
}

foo函数的声明被提升了,因此在第一行调用可以正常进行。 每个作用域都会进行提升操作,foo函数自身会在内部对var a;进行提升。因此上面这段代码可以理解为如下:

function foo(){
	var a;
	console.log(a);
	a = 2;
}
foo();

注意:函数声明会被提升,但是函数表达式不会被提升

foo(); // 这里会报错
var foo = function bar() {...}

这段程序中foo()被提升到所分配的作用域,因此foo()不会导致错误,但是foo此时还没有被赋值。foo()相当于undefined(),因此会抛出异常。 同时即使使用具名的函数表达式,名称标识符在赋值之前也无法在所在的作用域使用

foo(); // 报错
bar(); // 报错
var foo() = function bar() {...} // 具名函数

这段代码可以理解为如下代码:

var foo;
foo(); // 报错
bar(); // 报错
foo = function() {...}

3. 函数提升的特殊性与最佳实践

3.1 函数提升优先级

在JavaScript中,函数声明和变量声明都会被提升,但函数声明会被优先提升到变量声明之前。这个特性会导致一些有趣的行为:

示例1:函数声明vs变量声明

foo(); // 1
var foo;
function foo() {
	console.log(1);
}
foo = function() {
	console.log(2);
}

这段代码实际的执行顺序是:

function foo() {
	console.log(1);
}
var foo; // 被忽略,因为已经有同名函数声明
foo(); // 1
foo = function () {
	console.log(2);
}

3.2 函数声明覆盖规则

虽然重复的变量声明会被忽略,但后面的函数声明可以覆盖前面的函数声明。这可能导致意外的行为:

foo(); // 3
function foo() {
	console.log(1);
}
var foo = function (){
	console.log(2);
}
function foo() {
	console.log(3);
}

3.3 块级函数声明的陷阱

在块级作用域内声明函数需要特别注意。虽然函数声明会被提升,但在不同的JavaScript环境中可能有不同的行为:

foo(); // 报错
if(true) {
	function foo(){
		console.log(1);
	}
}else {
	function foo(){
		console.log(2);
	}
}

3.4 函数提升的最佳实践

为了避免函数提升带来的问题,建议遵循以下原则:

  1. 使用函数表达式替代函数声明

    • 使用const声明函数表达式,避免意外重新赋值
    • 函数表达式更清晰地表达了函数的作用域
  2. 避免在块级作用域中使用函数声明

    • 在块中使用函数表达式
    • 如果需要条件性地定义函数,使用变量声明配合函数表达式
  3. 保持函数声明在作用域顶部

    • 即使有提升机制,也应该将函数声明放在代码的顶部
    • 这样可以提高代码的可读性和可维护性

4. 小结

我们可能习惯的将var a = 2;当做一个声明,在实际中JavaScript引擎会将var a;和a = 2当做两个单独的声明,第一个是编译阶段的任务,第二个是执行阶段的任务。 这就意味着无论作用域中的声明出现在什么地方,都将在代码本身被执行前首先进行处理,所有声明的变量和函数都会被移动到各自作用域的最顶端,这个过程就是提升。声明本身会被提升,而包括函数表达式的赋值在内的赋值操作并不会提升。 要避免重复声明,特别是当普通的var声明和函数声明混合在一起的时候,否则会引起一些意想不到的问题。

💬 欢迎评论!请确保您已登录 GitHub。

最近更新: 2025/5/19 12:57
Contributors: chenshicong
Prev
作用域和闭包