什么是变量提升(Hoisting)和函数提升?两者的优先级是怎样的?
在 JavaScript 中,提升(Hoisting) 是指 JavaScript 引擎在执行代码之前,会将变量和函数的声明阶段移动到它们所在作用域的最顶部的这一行为。
需要注意的是,代码并没有真正在物理位置上被移动,而是在编译阶段(执行上下文创建阶段),引擎把变量和函数的声明放入了内存中。
以下是详细的解释和两者的优先级规则:
1. 变量提升(Variable Hoisting)
在使用 var 声明变量时,只有声明会被提升,赋值操作不会被提升,它会留在原地等待执行。
示例代码:
javascript
console.log(a); // 输出: undefined
var a = 10;
JS 引擎实际的理解方式:
javascript
var a; // 声明被提升到顶部,默认值为 undefined
console.log(a); // 此时 a 已声明但未赋值,所以是 undefined
a = 10; // 赋值操作留在原地
注意(ES6+): 使用
let和const声明的变量也会发生提升,但它们不会被初始化为undefined,而是进入所谓的暂时性死区(TDZ, Temporal Dead Zone)。如果在声明之前访问它们,会抛出ReferenceError错误。
2. 函数提升(Function Hoisting)
使用函数声明(function foo() {})定义的函数,整个函数体(包括声明和定义)都会被提升到作用域的最顶部。这意味着你可以在函数声明之前调用它。
示例代码:
javascript
sayHello(); // 正常执行,输出: "Hello!"
function sayHello() {
console.log("Hello!");
}
例外:函数表达式不会被完整提升
如果你使用变量赋值的形式定义函数(函数表达式或箭头函数),它遵循的是变量提升的规则,只有变量名会被提升。
javascript
sayHi(); // 报错: TypeError: sayHi is not a function
var sayHi = function() {
console.log("Hi!");
};
解释:var sayHi 被提升了,此时它的值是 undefined。当你尝试像函数一样调用 undefined() 时,就会报 TypeError。
3. 两者的优先级
当变量和函数同名时,提升的优先级规则非常明确:
函数提升的优先级高于变量提升。
具体表现为:
- 在编译阶段,如果遇到同名的变量声明和函数声明,函数声明会覆盖变量声明。
- 但是,如果后续代码在执行阶段对变量进行了赋值操作,这个赋值会覆盖掉之前的函数。
经典面试题示例:
javascript
console.log(foo); // 输出: [Function: foo]
var foo = 10;
function foo() {
console.log("I am a function");
}
console.log(foo); // 输出: 10
JS 引擎执行这段代码的步骤解析:
- 编译阶段(处理提升):
- 发现
function foo() {...},将其完整提升并存入内存。 - 发现
var foo,因为内存中已经存在名为foo的函数,JS 引擎会忽略这个var foo的声明(不会把foo变成undefined)。
- 发现
- 执行阶段(按顺序执行):
- 执行第一个
console.log(foo):此时foo还是那个函数,所以打印[Function: foo]。 - 执行
foo = 10:给foo赋值为 10,此时函数被数字覆盖。 - 执行第二个
console.log(foo):打印10。
- 执行第一个
总结
- 变量提升:
var声明提前,值为undefined;赋值留在原地。let/const声明提前,但在声明前不可用(TDZ)。 - 函数提升:
function声明和函数体完整提前,可以在定义前调用。 - 优先级:函数提升 > 变量提升。同名时,函数声明优先占位;但运行时的变量赋值可以重新覆盖同名函数。