【前端】JavaScript 变量声明与函数提升例题分析:深入理解变量提升、作用域链与函数调用
文章目录
- 💯前言
- 💯第一题:变量和函数声明的提升与函数内局部作用域
- 题目代码
- 代码解析与预解析结果
- 1. 全局作用域的预解析
- 2. 函数作用域 `fn` 的预解析
- 代码执行流程
- 最终输出
- 💯第二题:函数声明与变量赋值的相互覆盖
- 题目代码
- 代码解析与预解析结果
- 预解析步骤
- 代码执行过程
- 最终输出
- 💯第三题:函数作用域链与调用时作用域的区别
- 题目代码
- 作用域链与调用过程解析
- 预解析步骤
- 代码执行过程
- 最终输出
- 深入理解:为什么不会访问 `fn2` 中的变量?
- 如何模拟访问局部作用域?
- 💯总结与扩展:JavaScript 中的作用域与变量管理
- 1. 变量提升与函数提升的对比
- 2. 作用域链是静态的
- 3. 块作用域与变量声明
- 4. 函数表达式与函数声明
- 💯小结
💯前言
- 在 JavaScript 中,变量声明的提升、函数声明的提升以及
作用域链
是非常重要的概念。理解它们可以帮助我们更好地理解代码的执行顺序以及变量的可见性,避免在编码过程中出现一些难以理解的错误
。
在本文中,我们将通过三个示例代码逐步讲解这些核心概念,确保你能够全面深入地理解它们
。
JavaScript
💯第一题:变量和函数声明的提升与函数内局部作用域
题目代码
var x = 10;
function fn() {
x = 20;
console.log(x); // 这里打印值是多少?
var x = 30;
}
x = 30;
fn();
console.log(x); // 这里打印值是多少?
代码解析与预解析结果
在 JavaScript 执行代码之前,会先进行 预解析 ,把变量和函数声明提升到当前作用域的顶部。在这段代码中,我们有两个作用域需要考虑: 全局作用域 和 函数作用域 fn
。
1. 全局作用域的预解析
- 提升函数声明
fn
。 - 声明
var x
,初始值为undefined
。
在预解析后,全局代码的结构为:
var x; // 声明提升,初始值 undefined
function fn() { ... } // 函数声明提升
x = 10; // 赋值为 10
x = 30; // 赋值为 30
fn();
console.log(x);
2. 函数作用域 fn
的预解析
在 fn
函数中:
- 提升
var x
,此时x
的初始值为undefined
。 - 赋值
x = 20
和console.log(x)
的执行在变量声明之后。
所以,函数内部的代码结构如下:
function fn() {
var x; // 提升,初始值为 undefined
x = 20; // 赋值操作
console.log(x); // 打印 20
x = 30; // 赋值操作
}
代码执行流程
- 全局变量
x
初始值为10
,后被赋值为30
。 - 调用
fn
函数时,由于fn
内部的x
变量声明提升,导致x
的初始值为undefined
,在执行x = 20
后,console.log(x)
打印的是20
。 - 全局作用域中的
console.log(x)
打印30
。
最终输出
fn
内的console.log(x)
: 输出20
。- 全局的
console.log(x)
: 输出30
。
💯第二题:函数声明与变量赋值的相互覆盖
题目代码
console.log(x); // 这里打印值是多少?
var x = function () {
x = 90;
};
x();
var x = 30;
function x() {
x = 40;
}
console.log(x); // 这里打印值是多少?
代码解析与预解析结果
预解析步骤
- 全局作用域提升:
- 函数
x
的声明提升到顶部。 - 变量
var x
提升,初始值为undefined
。
预解析后的代码结构:
var x; // 声明提升,初始值为 undefined
function x() {
x = 40;
}
console.log(x);//函数体
x = function () {
x = 90;
}
x();
x = 30;
console.log(x);//30
代码执行过程
- 第一次
console.log(x)
: 打印函数体,因为函数x
的声明优先级高于变量声明,且x
是函数。 x = function () {...}
: 重写了变量x
,x
不再是函数,而是一个匿名函数。x()
: 调用匿名函数,将x
赋值为90
。x = 30
: 重新将x
赋值为30
。- 第二次
console.log(x)
: 输出30
。
最终输出
- 第一次
console.log(x)
: 打印函数体。 - 第二次
console.log(x)
: 输出30
。
💯第三题:函数作用域链与调用时作用域的区别
题目代码
var x = 20;
function fn1() {
console.log(x); // 这里打印值是多少?
}
x = 30;
function fn2() {
var x = 1;
fn1();
}
x = 40;
fn2();
作用域链与调用过程解析
在 JavaScript 中,函数的作用域链在函数定义时就已经确定,而不是在函数调用时动态改变的。这意味着 fn1
的作用域链不会包含 fn2
的局部作用域 ,即使 fn1
是在 fn2
中调用的。
预解析步骤
- 全局作用域提升:
- 函数
fn1
和fn2
提升到顶部。 - 变量
var x
提升,初始值为undefined
。
预解析后的代码结构:
var x; // 提升
function fn1() {
console.log(x);
}
function fn2() {
var x; // 提升
x = 1;
fn1();
}
x = 20;
x = 30;
x = 40;
fn2();
代码执行过程
x = 20
,然后更新为30
。x = 40
,全局变量x
最终值为40
。- 调用
fn2()
:
- 在
fn2
内,声明并赋值局部变量x = 1
。 - 调用
fn1()
时,fn1
的作用域链中没有fn2
的局部变量,只有全局变量。 - 因此,
fn1
中的console.log(x)
打印全局变量的值,即40
。
最终输出
fn1
内的console.log(x)
: 输出40
,因为作用域链是静态的,函数fn1
只能访问定义时所在的全局变量。
深入理解:为什么不会访问 fn2
中的变量?
- 作用域链是静态的 :当函数
fn1
定义时,它的作用域链已经被锁定,只包含全局作用域,而fn2
的局部作用域不会成为fn1
的一部分。 - 调用栈与作用域链的区别 :调用栈记录了函数的调用顺序,但不会影响作用域链的解析。
如何模拟访问局部作用域?
- 传递参数 :将
fn2
的局部变量作为参数传递给fn1
。
function fn1(x) {
console.log(x);
}
function fn2() {
var x = 1;
fn1(x); // 传递参数
}
fn2(); // 输出 1
- 在
fn2
中重新定义fn1
:使fn1
成为fn2
的内部函数,这样它就能访问fn2
的局部变量。
function fn2() {
var x = 1;
function fn1() {
console.log(x); // 可以访问 `fn2` 的局部变量
}
fn1();
}
fn2(); // 输出 1
💯总结与扩展:JavaScript 中的作用域与变量管理
1. 变量提升与函数提升的对比
- 变量提升 :
var
声明的变量会被提升,但初始化仍在原位置。 - 函数提升 :函数声明会被提升到作用域顶部,且可以在声明之前调用。
2. 作用域链是静态的
- 函数的作用域链在定义时就确定下来,调用位置不会影响它的作用域链。
3. 块作用域与变量声明
let
和const
:与var
不同,它们会在块级作用域内提升,但在被赋值之前无法访问(即存在“暂时性死区”)。- 使用
let
和const
可以有效避免变量提升带来的潜在问题,建议在开发中优先使用。
4. 函数表达式与函数声明
- 函数声明 会提升,而 函数表达式 不会。
- 如果需要确保函数只能在特定位置后调用,应该使用函数表达式。
💯小结
通过这篇文章的三个题目,我们深入理解了 JavaScript 中的变量提升、函数提升和作用域链
的复杂机制。这些概念是 JavaScript 的基础,也是编码中最容易引发错误
的部分。只有对它们有清晰的理解,才能避免代码中的陷阱,更高效地编写JavaScript 程序
。