变量声明需谨慎!!!💣这几种声明变量的方式(var、let、const)还有作用域,绝不能含糊!
引言
JavaScript作为一门动态脚本语言,其变量声明机制和作用域规则一直是我们需要深入理解的核心内容。从早期的var
到ES6引入的let
和const
,JavaScript的变量管理方式经历了显著的变化。今天,我们从多个角度,全面解析var
、let
和const
的异同。
一、JS代码的执行机制
对于给定的JS代码文件,首先要做的是将其从硬盘读入内存,然后开始执行。JavaScript代码的执行依赖于引擎,如Chrome的V8引擎。V8负责将代码从硬盘读入内存后,进行解析、编译和优化。其核心流程分为两个阶段:
- 编译阶段:引擎对代码进行词法分析、语法分析,并确定作用域规则。
- 执行阶段:逐行执行代码,处理变量赋值、函数调用等操作。
二、作用域与作用域链
2.1 作用域的类型
作用域是变量和函数的可访问性规则,JavaScript中分为三类:
- 全局作用域:在函数或代码块外声明的变量,全局可访问。
- 函数作用域:在函数内部声明的变量,仅函数内可见。
-
块级作用域(ES6新增):由
{}
包裹的代码块(如if
、for
),使用let
或const
声明的变量仅块内有效。
2.2 作用域链的运作机制
当访问一个变量时,引擎会按照作用域链逐层查找:
当前作用域 → 父级作用域1 → 父级作用域2 → ... → 全局作用域
(ps:父作用域可嵌套)
这种链式结构确保了变量的层级隔离性。
function outer() {
let a = 10;
function inner() {
console.log(a); // 通过作用域链找到outer的a
}
inner();
}
outer(); // 输出10
三、变量提升(Hoisting)
3.1 var的变量提升
在编译阶段,var
声明的变量会被提升到作用域顶部,并初始化为undefined
,而赋值操作保留在执行阶段。
示例:
console.log(x); // undefined
var x = 5;
console.log(x); // 5
等效于:
var x; // 提升声明
console.log(x); // undefined
x = 5; // 赋值
console.log(x); // 5
3.2 let的变量提升
let声明的变量也会被提升到其所在的作用域顶部,但与var不同,let声明的变量在初始化之前会进入一个“暂时性死区”(Temporal Dead Zone, TDZ)。在这个区域内,变量是“提升”了,但尚未初始化,因此不能被访问,任何尝试访问这些变量的操作都会抛出[ReferenceError]
console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 10;
3.3 函数声明提升
函数声明整体被提升,提升的是定义,而不是调用,因此可以在声明前调用:
showName() // 驼峰式命名
console.log(myName);
var myName = 'wym'
function showName() {
let b = 2;
console.log(myName)
console.log('函数执行了')
}
运行结果:
解析:这段代码等效于
function showName() {
let b = 2;
console.log(myName)
console.log('函数执行了')
}
showName()
console.log(myName)
var myName = 'wym'
注意:函数和变量之间,函数通常先于变量提升,即:优先提升函数,后提升变量。
四、var、let、const的全面对比
4.1 作用域差异
关键字 | 作用域 | 重复声明 | 变量提升 | 必须初始化 |
---|---|---|---|---|
var | 函数/全局 | 允许 | 是 | 否 |
let | 块级 | 不允许 | 否(TDZ) | 否 |
const | 块级 | 不允许 | 否(TDZ) | 是 |
4.2 使用场景分析
- var:ES6之前的主要声明方式,因作用域和提升问题,现不推荐使用。
- let:适用于需要重新赋值的块级变量(如循环计数器)。
- const:声明常量或引用类型(对象、数组),确保变量指向不变。
五、建议
定义变量时如果后续不需要修改了,建议优先使用const,提高代码可读性和安全性;次选let,杜绝重复声明报错,作用域更安全,其独特的TDZ机制也能够有效防止在声明前访问变量;var能不用就不用,防止变量污染和意外覆盖。