首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【前端】JavaScript 的神奇世界:作用域、闭包和变量提升的深入探索

【前端】JavaScript 的神奇世界:作用域、闭包和变量提升的深入探索

作者头像
CSDN-Z
发布2025-06-02 10:14:25
发布2025-06-02 10:14:25
10900
代码可运行
举报
文章被收录于专栏:AIGCAIGC
运行总次数:0
代码可运行

博客主页: [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: 前端

💯前言

  • JavaScript 是一种动态、灵活且功能强大的编程语言,被广泛用于现代 Web 开发 中。然而,JavaScript作用域、闭包 和 变量提升 等特性常常让初学者甚至有经验的开发人员感到困惑。在这篇文章中,我们将深入探讨这些复杂而有趣的概念,通过分析几个经典的代码示例,揭示 JavaScript 背后隐藏的执行逻辑和原理。例如,变量提升 在代码执行中的优先级问题,闭包 在函数中对外部变量的捕获行为,都会在文中一一讲解。本篇内容 旨在帮助读者更好地理解 JavaScript 语言的独特行为,掌握 varletconst 的适用场景,提升代码编写的可靠性以及阅读他人代码的能力。 JavaScript

💯第一题:数组操作与动态扩展


题目描述

有两个数组,数组 A 和数组 B。

  • 数组 A 为固定初始化数组 ['jojo', 'okko', '张三', '秃头', '帅小伙']
  • 数组 B 为用户动态选择添加的值,可能为 [] 或其他不确定的元素。
  • 目标是将数组 B 中前 N 位添加到数组 A 中,使得数组 A 的长度达到 7,其中 N = 7 - 数组A.length
  • 数组 A 有限制长度为 7

例如:

  • 数组 A['jojo', 'okko', '张三', '秃头', '帅小伙']
  • 数组 B['你好', '小脑斧', '大西瓜', '长得帅']
  • 输出['jojo', 'okko', '张三', '秃头', '帅小伙', '你好', '小脑斧']

代码实现

以下是完成上述要求的代码实现:

代码语言:javascript
代码运行次数:0
运行
复制
function fillArray(arrA, arrB) {
    var maxLen = 7; // 定义目标数组的最大长度为 7
    var arrALen = arrA.length; // 获取数组 A 当前的长度
    return arrA.concat(arrB.slice(0, maxLen - arrALen)); 
    // 使用 concat 方法将数组 A 和数组 B 的前 N 个元素合并成一个新数组
    // 其中 N = maxLen - arrALen
}

console.log(fillArray(['jojo', 'okko', '张三', '秃头', '帅小伙'], ['你好', '小脑斧', '大西瓜', '长得帅']));

详细解析

函数定义与参数

  • 定义了一个函数 fillArray(arrA, arrB),接收两个参数 arrAarrB,分别表示固定的数组 A 和动态的数组 B。

变量 maxLen

  • 使用变量 maxLen 表示目标数组的最大长度,这里固定为 7

计算 arrALen

  • 使用 arrA.length 获取数组 A 的当前长度,存储到变量 arrALen 中。

截取数组 B 的前 N 个元素

  • 通过计算 maxLen - arrALen,得出还需要从数组 B 中取出多少个元素,这里用 slice(0, maxLen - arrALen) 来截取数组 B 的前 N 个元素。

合并数组

  • 使用 concat 方法,将数组 A 与从数组 B 中截取的前 N 个元素合并,返回一个新的数组(不会修改原数组 A 和 B)。

输出结果

对于输入的数组 A 为 ['jojo', 'okko', '张三', '秃头', '帅小伙'],数组 B 为 ['你好', '小脑斧', '大西瓜', '长得帅'],最终输出为:

代码语言:javascript
代码运行次数:0
运行
复制
['jojo', 'okko', '张三', '秃头', '帅小伙', '你好', '小脑斧']

代码特点与优点

  • 灵活性:代码通过计算需要的元素数量,确保从数组 B 中取出的元素恰好能够填满数组 A,使其长度达到目标值 7
  • 安全性:使用 slice 截取元素的方式,不会对原始数组 B 造成修改,保证了数据的完整性。
  • 简洁性:利用 concatslice 的组合,代码显得简洁明了,一步到位地解决了数组合并的问题。

💯第二题:变量提升与作用域链


题目描述

以下是我们要分析的代码,这段代码涉及 JavaScript 的变量提升和作用域链特性:

代码语言:javascript
代码运行次数:0
运行
复制
console.log(x); 
f1();
var x = 0;
function f1() {
    f2();
    var x = 10;
    function f2() {
        x = 20;
        console.log(x);
    }
}
console.log(x);

预解析后的代码

在 JavaScript 中,变量提升和函数声明提升的机制,会使代码的执行顺序变得更加复杂。以下是预解析后的代码形式,展示了变量和函数如何被提升:

代码语言:javascript
代码运行次数:0
运行
复制
var x;
function f1(){
    var x;//局部变量x
    function f2() {
        x = 20;//局部变量x
        console.log(x);//20
    }
    f2();
    x = 10;//局部变量
}
console.log(x);//undefined;
f1();
x = 0;//全局变量x
console.log(x);//0

详细解析

  1. 变量与函数提升
    • JavaScript 中,var 声明的变量会被提升到作用域顶部,但不会进行赋值。因此,var x 在预解析时被提升,但初始值为 undefined
    • 函数声明 f1f2 也会被提升到作用域顶部,因此它们可以在声明之前调用。
  2. 执行顺序
    • console.log(x):在这行代码中,变量 x 已被提升,但尚未赋值,所以输出 undefined
    • 调用 f1():进入函数 f1 后,首先调用 f2()
      • f2() 中,x = 20 实际上修改的是 f1 作用域内的局部变量 x,由于 var x = 10f1 内部,x 的作用域是 f1 函数级别的。
      • console.log(x):输出 20,因为 f2() 修改了 f1 内部的局部变量 x
    • x = 0:为全局变量 x 赋值为 0
    • console.log(x):最后输出 0,因为此时全局变量 x 已被赋值为 0

输出结果

代码语言:javascript
代码运行次数:0
运行
复制
undefined
20
0

代码特点与重点

  • 变量提升与未赋值:即使变量 x 被提升,它在赋值之前的初始值为 undefined
  • 函数作用域链:函数 f2 修改了 f1 内部的局部变量 x,展示了 JavaScript 的函数作用域链和变量查找规则。
  • 全局与局部变量的区分:全局变量 x 与函数内局部变量 x 并不冲突,且具有不同的作用域,这些特性使得 JavaScript 的执行行为更加灵活。

💯第三题:函数与变量同名的提升行为


题目描述

以下代码涉及 JavaScript 中函数声明和变量声明同名时的提升行为:

代码语言:javascript
代码运行次数:0
运行
复制
console.log(a);
function a() {
    a = 20;
}
a();
console.log(a);
var a = 30;
console.log(a);

预解析后的代码

在 JavaScript 中,函数声明和变量声明同名时,函数声明的优先级更高,以下是预解析后的代码形式:

代码语言:javascript
代码运行次数:0
运行
复制
var a;
function a(){
    a = 20;//全局变量
}
console.log(a);//function a(){ a = 20; }
a();
console.log(a);//20
a = 30;
console.log(a);//30

详细解析

  1. 提升过程
    • 函数声明 a 被提升到作用域顶部,优先于变量 a 的声明。
    • 变量 a 也被提升,但因为已经有同名的函数声明,所以它的作用被函数占据。
  2. 执行过程
    • console.log(a):因为 a 是函数声明提升的,所以输出整个函数体。
    • 调用 a():调用函数 a,在函数内部执行 a = 20,将 a 的值修改为 20。这里的 a 指向的是全局的标识符。
    • console.log(a):输出 20,因为 a 已经被赋值为 20
    • a = 30:将全局变量 a 赋值为 30
    • console.log(a):输出 30

输出结果

代码语言:javascript
代码运行次数:0
运行
复制
function a() { a = 20; }
20
30

代码特点与重点

  • 函数声明优先级:在 JavaScript 中,同名的函数声明优先级高于变量声明,函数会覆盖变量的标识符。
  • 修改全局变量:函数内部对 a 的赋值,实际上是对全局范围内的 a 的修改。
  • 执行顺序的影响:理解提升的优先级有助于更好地预测代码的输出行为,避免在开发中遇到意外的结果。

💯第四题:闭包与变量捕获


题目描述

以下代码展示了 JavaScript 中的闭包现象,特别是在循环中对变量的捕获行为:

代码语言:javascript
代码运行次数:0
运行
复制
function f1() {
    var arr = [];
    for (var i = 0; i < 10; i++) {
        arr.push(function () {
            return i;
        });
    }
    return arr;
}

console.log(f1()[3]);
console.log(f1()[3]());

预解析后的代码

在 JavaScript 中,var 声明的变量是函数作用域的,因此在循环中,所有闭包捕获的是同一个变量引用,而不是独立的值。以下是预解析后的代码及其解释:

代码语言:javascript
代码运行次数:0
运行
复制
function f1() {
    var arr;
    var i;
    arr = [];
    for (i = 0; i < 10; i++){
        arr.push(function(){
            return i;
        })
    }
    return arr;
}

console.log(f1()[3]); 
console.log(f1()[3]());

详细解析

  1. var 的作用域
    • f1 函数中,变量 i 是通过 var 声明的,这意味着 i 的作用域是整个 f1 函数,而不是块级作用域。
    • 当循环结束时,i 的值已经增加到 10,所以所有闭包在执行时,访问的都是同一个变量 i,其值为 10
  2. 匿名函数的行为
    • f1()[3]:这里直接访问数组中存储的第四个函数,结果是函数体本身,而不是调用结果。
    • f1()[3]():在这里调用数组中的匿名函数,因为所有闭包引用的是共享的变量 i,而此时 i 的值为 10,所以输出 10
  • 仅打印 f1()[3]:当直接打印 f1()[3] 时,输出的是存储在数组 arr 中的函数本身,即函数体。这是因为 f1() 返回了包含函数的数组,访问 f1()[3] 仅仅是指向该函数,而并没有执行它。
  • 调用 f1()[3]():当执行 f1()[3]() 时,实际上调用了存储在数组中的匿名函数。由于这个函数捕获的是共享的变量 i,而在循环结束时,i 的值为 10,因此输出结果为 10

如何解决这个问题?

为了让每个闭包捕获各自独立的 i 值,可以使用 let 声明或立即执行函数表达式(IIFE)来创建新的作用域。


解决方法1:使用 let

let 是块级作用域,因此每次循环都会创建一个新的 i,从而保证每个闭包捕获的都是不同的 i 值。

代码语言:javascript
代码运行次数:0
运行
复制
function f1() {
    var arr = [];
    for (let i = 0; i < 10; i++) {
        arr.push(function () {
            return i;
        });
    }
    return arr;
}

console.log(f1()[3]()); // 输出 3

解决方法2:使用 IIFE

通过立即执行函数表达式(IIFE),每次循环创建一个新的作用域,并将当前的 i 传递进去。

代码语言:javascript
代码运行次数:0
运行
复制
function f1() {
    var arr = [];
    for (var i = 0; i < 10; i++) {
        (function (j) {
            arr.push(function () {
                return j;
            });
        })(i);
    }
    return arr;
}

console.log(f1()[3]()); // 输出 3

输出结果

代码语言:javascript
代码运行次数:0
运行
复制
function () { return i; } // 原始代码输出结果(f1()[3])
10  // 原始代码中调用闭包函数的输出结果(f1()[3]())
3   // 使用 let 或 IIFE 修改后的输出结果

代码特点与重点

  • 闭包捕获的引用:在原始代码中,所有闭包捕获的是同一个变量 i,因此最终输出的都是相同的值。
  • 使用 let 创建块级作用域:使用 let 可以为每次循环创建新的作用域,使得每个闭包能够捕获独立的变量。
  • 立即执行函数表达式(IIFE):通过 IIFE,我们可以人为地创建出新的作用域,从而使得每个闭包捕获独立的变量值。这是 ES6 之前常用的解决方案。

💯小结

通过本文对 JavaScript作用域、闭包 和 变量提升 等概念的深入探索,我们可以更清楚地理解 JavaScript 的执行机制及其复杂之处。我们学习了如何在数组操作中动态添加元素,理解了 变量提升 的影响,并认识到 函数声明变量声明 同名时的优先级。 此外,闭包与循环中的变量捕获问题,也让我们明白了 var 的缺陷及其在闭包中的表现。通过这些代码实例,我们不仅揭示了 JavaScript 代码的执行细节,还学会了如何规避由提升和闭包带来的常见问题。希望这些知识能帮助你编写出更加健壮、可维护的 JavaScript 代码,在项目开发中少走弯路

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-12-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 💯前言
  • 💯第一题:数组操作与动态扩展
    • 题目描述
    • 代码实现
    • 详细解析
    • 代码特点与优点
  • 💯第二题:变量提升与作用域链
    • 题目描述
    • 预解析后的代码
    • 详细解析
    • 输出结果
    • 代码特点与重点
  • 💯第三题:函数与变量同名的提升行为
    • 题目描述
    • 预解析后的代码
    • 详细解析
    • 输出结果
    • 代码特点与重点
  • 💯第四题:闭包与变量捕获
    • 题目描述
    • 预解析后的代码
    • 详细解析
    • 如何解决这个问题?
      • 解决方法1:使用 let
      • 解决方法2:使用 IIFE
    • 输出结果
    • 代码特点与重点
  • 💯小结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档