
函数声明与赋值的行为经常是开发者在学习中遇到的难点。本文通过对几道经典的代码解析题目进行详细剖析,来帮助你更好地理解 JavaScript 的执行顺序和作用域链。我们会一一讨论题目中的预解析结果、具体的输出以及背后的原因。
JavaScript

var x = 20;
function fn() {
if (x) {
var x = 30;
}
console.log(x); // 这里打印值是多少?
}
fn();
console.log(x); // 这里打印值是多少?fn 中的 var x 也会被提升到函数顶部。预解析后的代码结构如下:var x; // 全局变量声明提升,初始值为 undefined
function fn() {
var x; // 局部变量声明提升,初始值为 undefined
if (x) {
x = 30; // 不会执行,因为 x 是 undefined,条件为 false
}
console.log(x); // 输出 undefined
}
// 执行阶段
x = 20; // 全局变量赋值
fn(); // 调用函数 fn,进入局部作用域
console.log(x); // 输出 20var x 提升到全局作用域顶部,初始值为 undefined。fn 提升到全局作用域顶部,完整保留。fn() 函数:
fn 内的 var x 被提升到函数顶部,初始值为 undefined。if (x) 语句中,x 的值为 undefined,所以条件为 false,不会进入 if 块。console.log(x) 输出的是局部变量 x 的值,即 undefined。console.log(x) 输出全局变量的值 20。fn() 内的 console.log(x) 输出:undefined。console.log(x) 输出:20。
x 遮蔽了全局变量 x,即使局部变量未赋值。if (x) 判断时,使用的是函数作用域中的局部 x,初始值是 undefined。
function fun(param) {
console.log(param); // 这里打印值是多少?
var param = function () {
console.log(1);
};
console.log(param); // 这里打印值是多少?
}
fun(5);function fun(param) {
// 参数 `param` 被提升,作为函数的局部变量,初始值为传递的参数值 5
console.log(param); // 输出 5
param = function () { // 赋值操作覆盖了参数的值
console.log(1);
};
console.log(param); // 输出函数体
}
// 执行阶段
fun(5);fun(5) 时,参数 param 被赋值为 5。console.log(param): 5,因为此时 param 的值是传递的参数。param = function() { console.log(1); }: param 被赋值为一个函数,覆盖了之前的参数值。console.log(param): function () { console.log(1); },因为此时 param 已经被覆盖为该函数。console.log(param) 输出:5。console.log(param) 输出:function () { console.log(1); }。

var 声明。var param 的声明被忽略,但赋值操作仍然执行,因此覆盖了参数 param 的值。
var foo = 1;
function bar() {
function foo() {}
foo = 10;
console.log(foo); // 这里打印值是多少?
}
bar();
console.log(foo); // 这里打印值是多少?var foo; // 全局变量声明,初始值 undefined
function bar() {
function foo() {} // 局部变量 foo,初始值为函数
foo = 10; // 覆盖局部变量 foo 的值
console.log(foo); // 输出 10
}
// 执行阶段
foo = 1; // 全局变量赋值
bar(); // 调用 bar 函数
console.log(foo); // 输出 1var foo 提升,初始值为 undefined。bar 提升到全局作用域顶部。bar 函数中,function foo() 声明被提升到 bar 内部顶部,声明了一个局部变量 foo,初始值为函数。bar() 时,局部变量 foo(最初是一个函数)被重新赋值为 10。console.log(foo) 输出 10,因为局部变量 foo 已被赋值为 10。console.log(foo) 输出全局变量的值 1,因为全局变量 foo 没有被 bar 内部的操作影响。bar() 内的 console.log(foo) 输出:10。console.log(foo) 输出:1。

function foo() 在 bar 中被提升,相当于声明了一个局部变量。foo = 10 是对局部变量的重新赋值,不会影响全局变量。在 JavaScript 中,函数声明的提升优先于变量声明。这意味着函数会被完整地提升到作用域的顶部,并且在变量声明之前可以使用。但变量声明只会被提升声明,赋值操作仍然保留在原地。
在题目三中,function foo() 实际上相当于声明了一个局部变量 foo,并将其初始化为一个函数对象。正因如此,当后续的 foo = 10 进行赋值时,修改的是这个局部变量,而不是全局的 foo。
这说明函数声明不仅仅是定义一个可调用的代码块,它也会在当前作用域中创建一个变量,这个变量的名字与函数名相同。
当函数内部声明一个与外部变量同名的变量时,这个内部变量会遮蔽外部的变量,这就是所谓的作用域链中的遮蔽现象。遮蔽意味着在当前作用域中,同名的外部变量变得不可访问,除非使用特定的方式(例如 this 或全局对象)访问全局变量。

通过以上几个例子,我们深入理解了 JavaScript 中的作用域、变量提升、函数声明与变量声明之间的关系。关键点包括:
var 声明。
预解析过程有助于理解代码的执行顺序,尤其是复杂的作用域和变量提升问题。