1. var
的变量提升(Hoisting)
定义
- 变量提升:
var
声明的变量在代码执行前会被提升到当前作用域的顶部,但 赋值操作留在原地。 - 本质是 JavaScript 引擎的预编译阶段对变量声明的处理。
代码示例
console.log(a); // 输出 undefined(变量提升但未赋值)
var a = 10;
console.log(a); // 输出 10
等价于:
var a; // 声明提升到作用域顶部
console.log(a); // undefined
a = 10; // 赋值留在原地
console.log(a); // 10
特点
- 变量在声明前可访问(值为
undefined
)。 - 函数作用域生效(无块级作用域)。
注意事项
- 函数声明优先级高于变量声明:
console.log(foo); // 输出函数体(函数声明提升优先级高) var foo = 20; function foo() {}
2. let/const
的暂时性死区(TDZ)
定义
- 暂时性死区(TDZ):在代码块内,使用
let/const
声明的变量在声明前不可访问,否则抛出ReferenceError
。 - 目的是强制开发者先声明后使用,避免隐式错误。
代码示例
console.log(b); // ❌ ReferenceError: Cannot access 'b' before initialization
let b = 20;
if (true) {
console.log(c); // ❌ ReferenceError(TDZ 开始)
const c = 30; // TDZ 结束
}
原理
- 变量在声明前处于 TDZ,引擎会检测此阶段的非法访问。
let/const
声明不会提升到作用域顶部,但会绑定到块级作用域。
注意事项
- TDZ 与作用域链:
let x = 1; { console.log(x); // ❌ ReferenceError(当前块级作用域的 x 在 TDZ) let x = 2; }
3. 块级作用域实现
定义
- 块级作用域:由
{}
包裹的代码块(如if/for
)形成独立作用域,let/const
声明的变量仅在此块内有效。 - ES6 通过
let/const
引入块级作用域,解决var
的变量泄露问题。
代码示例
// var 的变量泄露
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i)); // 输出 3, 3, 3
}
// let 的块级作用域
for (let j = 0; j < 3; j++) {
setTimeout(() => console.log(j)); // 输出 0, 1, 2
}
实现原理
- 每次循环迭代生成一个新的块级作用域,
let
声明的变量绑定到当前作用域。 - 块级作用域内的变量在外部不可访问:
{ let blockVar = "内部"; } console.log(blockVar); // ❌ ReferenceError
应用场景
- 循环中的计数器隔离。
- 条件语句中的临时变量保护。
- 避免全局污染。
总结对比表
特性 | var | let/const |
---|---|---|
作用域 | 函数作用域 | 块级作用域 |
变量提升 | ✅(值为 undefined ) | ❌(TDZ 报错) |
重复声明 | ✅ 允许 | ❌ 报错 |
全局变量绑定 | ✅(window 对象) | ❌(全局但不在 window ) |
最佳实践
- 优先使用
const
:除非需要重新赋值。 - 避免使用
var
:除非需要兼容旧环境。 - 利用块级作用域:限制变量生命周期,减少命名冲突。
- 注意 TDZ:始终先声明后使用。
通过理解这些机制,可以更好地掌握 JavaScript 的作用域和变量行为,避免常见陷阱。
底层原因
var bar = {
myName:"11",
printName: function () {
console.log(this.myName)
}
}
function foo() {
let myName = "22"
return bar.printName
}
var myName = "33"
let _printName = foo()
_printName()//33
bar.printName()//11
1. 编译阶段(变量提升与作用域创建)
- 全局对象(Global Object,浏览器中为window):
{ bar: undefined, // var声明,初始化为undefined foo: <Function>, // 函数声明整体提升 myName: undefined // var声明,初始化为undefined }
- 词法环境(Lexical Environment):
{ _printName: <uninitialized> // let声明,未初始化(暂时性死区) }
2. 执行阶段
-
变量赋值与函数执行:
bar
被赋值为对象:bar = { myName: "11", printName: function() {...} }
var myName = "33"
执行,全局对象的myName
变为"33"
。foo()
被调用:foo
的函数作用域:{ myName: "22" // let声明,局部变量 }
return bar.printName
:返回bar.printName
的函数引用(未绑定bar
的this
,函数在调用时才会绑定this)。
_printName
被赋值为bar.printName
的函数引用。
-
关键函数调用分析:
_printName()
:- 相当于直接调用函数
function() { console.log(this.myName) }
。 - 非严格模式下,
this
指向全局对象(window
),因此this.myName
找到全局的"33"
。
- 相当于直接调用函数
bar.printName()
:- 作为方法调用,
this
指向bar
,因此输出bar.myName
(即"11"
)。
- 作为方法调用,
-
var
与let
的作用域差异:var
声明的变量(如myName
)会挂载到全局对象(window.myName
)。let
声明的变量(如_printName
)存在于词法环境中,但不在全局对象上。
-
this
的指向逻辑:- 函数调用方式决定
this
:_printName()
作为独立函数调用,this
默认指向全局对象(非严格模式)。- 若在严格模式(
"use strict"
)下,this
为undefined
,会导致报错。 bar.printName()
作为方法调用,this
指向bar
。
- 函数调用方式决定
-
执行顺序的准确性:
var myName = "33"
在foo()
调用之前执行,因此全局的myName
已为"33"
。foo()
内部的let myName = "22"
是局部变量,不影响全局。