JavaScript 面试笔记:作用域、变量提升、暂时性死区与 const
的可变性
在这篇笔记中,我将总结一些常见的 JavaScript 面试问题,特别是关于 作用域 、变量提升 、暂时性死区(TDZ) 和
const
的可变性 这四个关键概念的理解。这些概念不仅是面试中的常考内容,也是日常编码中容易迷惑的地方。通过这篇笔记,希望我能理清这些思路,为今后的学习和面试打下更扎实的基础。
一、作用域的差异:var
与 let
/const
的区别
在学习和面试中,作用域的差异是最常见的考点之一。通过不断的思考和总结,我逐渐理解了 var
、let
和 const
在作用域上的差异。
var
是函数级作用域 :即使它在一个块级结构(如if
或for
)中声明,作用域仍然是整个函数,导致var
声明的变量会泄漏到函数外面。因此,即使在if
块内声明了var
变量,它仍然在整个函数内有效。let
和const
是块级作用域:它们的作用范围只在声明它们的代码块内有效,也就是说,它们不会泄漏到外部。
通过这个思考,我得出以下结论:如果在一个
if
语句和一个函数内部声明变量,var
声明的变量会在整个函数作用域内可访问,而let
和const
只在{}
这个块内有效,外部无法访问。这让我意识到let
和const
在维护变量作用域时的优势,尤其在处理复杂的代码块时,它们能有效减少变量泄漏的问题。
js
// 使用 var
function testVar() {
if (true) {
var x = 5; // var 在函数级作用域内声明
}
console.log(x); // 输出 5,x 不仅仅在 if 块内有效,而是在整个函数内有效
}
testVar();
js
// 使用 let
function testLet() {
if (true) {
let y = 10; // let 在块级作用域内声明
}
console.log(y); // ReferenceError: y is not defined
}
testLet();
通过这两段代码,我得到了对
var
和let
作用域差异的直观理解。var
会导致变量泄漏,而let
和const
提供了更安全的块级作用域,避免了这种问题。
🌍 全局对象属性补充
在全局作用域下,var
声明的变量不仅会在全局范围内生效,还会被挂载到全局对象 上。
在浏览器中,这个全局对象是 window
;在 Node.js 中,则是 global
。
js
var a = 1;
let b = 2;
const c = 3;
console.log(window.a); // 1
console.log(window.b); // undefined
console.log(window.c); // undefined
通过这个实验我发现:
window.a
可以取到值 1;- 而
window.b
和window.c
都是undefined
。
这说明:
var
声明的变量会成为全局对象的属性,而
let
和const
则不会。
也就是说,var
不仅是函数级作用域,还会"污染"全局环境;
而 let
和 const
则保持更干净的作用域边界,更符合现代 JavaScript 的设计理念。
二、变量提升:var
、let
和 const
的行为差异
接下来,我思考了 变量提升 这个问题,特别是在我们声明变量之后,如果在声明之前访问它,会发生什么?
var
变量提升 :var
声明的变量会被提升到函数顶部(或全局作用域的顶部),但是它的赋值 不会被提升。因此,在声明之前访问var
变量时,它会返回undefined
,而不是抛出错误。
js
function testVar() {
console.log(a); // 输出 undefined,变量被提升但未初始化
var a = 10;
console.log(a); // 输出 10
}
testVar();
我发现,如果我在函数后面声明 var
变量并尝试访问它,JavaScript 会将其声明提升到函数顶部 ,但不会提升赋值。这导致第一次访问该变量时,返回的是 undefined
,而不是 ReferenceError
。
let
和const
变量提升(暂时性死区) :let
和const
变量会被提升,但它们不会在声明之前初始化。也就是说,在声明之前访问它们,会进入 暂时性死区(TDZ) ,并抛出ReferenceError
错误。
js
function testLet() {
console.log(b); // ReferenceError: Cannot access 'b' before initialization
let b = 20;
console.log(b); // 输出 20
}
testLet();
通过这个思考,我进一步理解了 let
和 const
的暂时性死区(TDZ) ,它是 JavaScript 引擎的一种保护机制,防止在变量声明之前访问变量,从而避免了潜在的错误。
三、暂时性死区(TDZ):是否在内存中"存在"?
关于 暂时性死区(TDZ) 是否在内存中"真正存在",我曾经有些疑惑。经过多次思考,我终于明白了,TDZ 本身并不是内存中的一个物理区域 ,而是 JavaScript 引擎的内部机制。
- 当我声明一个
let
或const
变量时,JavaScript 引擎会为该变量分配内存位置,但是变量的值不会立即初始化。在变量声明之前,该位置的内存会标记为"未初始化 ",因此访问该变量会触发 TDZ 错误(ReferenceError
)。 - TDZ 是执行时的行为,而不是内存中实际存在的区域。它是 JavaScript 引擎控制变量访问的一种方式,确保我们在变量初始化之前不会访问它。
js
function testLet() {
console.log(b); // 报错 ReferenceError: Cannot access 'b' before initialization
let b = 20;
console.log(b); // 输出 20
}
testLet();
在这段代码中,变量 b
会被提升,但由于它处于暂时性死区,在访问之前,JavaScript 引擎会抛出 ReferenceError
错误。这让我更加清楚地认识到,TDZ 是 JavaScript 引擎的一种作用域管理策略,而非内存中的一个物理区域。
四、const
的可变性:引用不变,内容可变
最后,我想分享我对
const
变量可变性的理解。这个问题有时会让人感到困惑,因为const
声明的变量不能重新赋值,但它并不代表值本身不能修改。
const
保证的是变量指向的引用不变 ,但对于对象和数组,引用的内容是可以修改的。
例如,使用 const
声明数组时,数组的内容是可以修改的,但不能重新赋值给一个新的数组。
js
const arr = [1, 2, 3];
arr.push(4); // 这行代码有效,修改数组内容
console.log(arr); // 输出 [1, 2, 3, 4]
arr = [5, 6]; // TypeError: Assignment to constant variable.
通过这段代码,我意识到 const
声明的数组内容是可以改变的,因为 const
保护的是引用本身 ,而不是数组内部的内容。重新赋值给整个数组会导致错误,因为 const
防止了对引用的修改。
五、总结
通过对
var
、let
、const
和TDZ
的深入理解,我不仅能更好地写出清晰、易于维护的代码,还能在面试中展示出对 JavaScript 执行机制的深刻理解。每个细节,哪怕是暂时性死区的存在与否,都让我能更深入地理解 JavaScript 的执行过程,帮助我在面试中表现更好。
这篇笔记记录了我对这些概念的思考过程和理解,也让我在未来遇到相关问题时能更自信地应对。