【前端】JavaScript作用域与预解析:深入理解问题与解答
文章目录
- 💯前言
- 💯题目一:局部变量与提升
- 代码分析:
- 预解析后的代码结构
- 详细执行过程
- 输出结果:
- 思考要点:
- 💯题目二:函数参数与变量提升
- 代码分析:
- 预解析后的代码结构
- 详细执行过程
- 输出结果:
- 思考要点:
- 💯题目三:函数声明与局部变量遮蔽
- 代码分析:
- 预解析后的代码结构
- 详细执行过程
- 输出结果:
- 思考要点:
- 💯拓展知识:函数声明与变量声明的提升
- 函数声明是局部变量的声明
- 变量遮蔽与作用域链
- 💯小结
💯前言
- 在 JavaScript 中,作用域、变量提升(Hoisting)、
函数声明与赋值
的行为经常是开发者在学习中遇到的难点。本文通过对几道经典的代码解析题目进行详细剖析,来帮助你更好地理解 JavaScript 的执行顺序和作用域链。我们会一一讨论题目中的预解析结果、具体的输出
以及背后的原因。
JavaScript
💯题目一:局部变量与提升
代码分析:
var x = 20;
function fn() {
if (x) {
var x = 30;
}
console.log(x); // 这里打印值是多少?
}
fn();
console.log(x); // 这里打印值是多少?
预解析后的代码结构
在 JavaScript 中,变量声明和函数声明会被提升到各自作用域的顶部。因此,函数 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); // 输出 20
详细执行过程
-
全局作用域提升阶段:
var 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);
预解析后的代码结构
在 JavaScript 中,当函数参数和局部变量同名时,参数的声明会被优先考虑,但局部变量的声明会被忽略,赋值操作仍然会执行。
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); // 输出 1
详细执行过程
-
全局作用域提升阶段:
var 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
声明。 -
函数声明是局部变量的声明:
在函数内部声明函数,相当于在当前作用域创建了一个局部变量,且初始值为函数对象。 -
变量遮蔽:
函数内部的变量会遮蔽外部的同名变量,函数声明也会有类似的行为。 -
预解析的重要性:
理解 JavaScript 的预解析过程
有助于理解代码的执行顺序,尤其是复杂的作用域和变量提升
问题。