JavaScript作用域详解

ops/2025/2/2 22:36:40/

前言
作用域是JavaScript中一个重要的概念,它决定了变量和函数在代码中的可访问性和可见性。了解JavaScript的作用域对于编写高效、可维护的代码至关重要。本文将深入介绍JavaScript作用域相关的知识点,其中包括作用域类型,作用域链,变量提升以及闭包等。

什么是作用域
在之前的文章中我们对TS中的模块进行了详细的分享,TS的在防止命名污染,冲突时采用的是命名空间,当我们看过TS命名空间的编译产物后我们就会知道,实际上其实际做法是采用JS的作用域实现一个独立的区域达到解决命名冲突和变量访问的可见性作用

JS中的作用域是一个存储变量、函数以及对象的位置,每个变量、函数和对象都被存储在一个特定的作用域中,它是指变量和函数在代码中的可访问范围,作用域决定了代码中哪些部分可以访问特定的变量和函数,通过作用域,我们可以将变量和函数封装在不同的作用域中,使其在合适的范围内可访问

就像上图中的示例,Foo1无法获取Foo2中的var4变量

作用域类型
常见的作用域类型包括全局作用域(Global Scope)、局部作用域(Local Scope)和块级作用域(Block Scope),JS也不例外

全局作用域
全局作用域是在整个代码中都可访问的作用域。在浏览器环境中,全局作用域通常是指window对象;在node环境下,则是globalThis或global

在全局作用域中声明的变量或函数是全局变量或全局函数,在代码任何地方都可以访问和使用
比如上面图示中的Foo4可以访问到全局作用域的变量及函数

javascript">const var1 = "var1";
const var2 = "var2";
function Foo1() {const var3 = "var3";
}
function Foo2() {const var4 = "var4";function Foo3() {}
}function Foo4() {console.log(var1, var2, Foo1, Foo2); // var1 var2 [Function: Foo1] [Function: Foo2]
}
Foo4();

局部作用域

JS中的局部作用域一般代指函数作用域(Function Scope),它是在函数内部声明的作用域,函数内部的变量和函数只能在函数内部访问,外部无法直接访问

就像上述Foo3的效果,可以访问到全局以及当前所在函数的作用域的变量及函数

javascript">const var1 = "var1";
const var2 = "var2";
function Foo1() {const var3 = "var3";
}
function Foo2() {const var4 = "var4";function Foo3() {console.log(var4, Foo3); // var4 [Function: Foo3]}Foo3();
}
Foo2();

块级作用域
块级作用域是在代码块(通常是由大括号{}包裹起来的部分)内声明的作用域。比如if(){...}、for(...){...}、try{...}等

ES6之前
在ES6之前,由于变量都是使用var声明的,所以没有块级作用域此类概念,只有全局作用域和函数(局部)作用域。那么需要模拟块级作用域如何怎么操作呢?

答案是使用立即执行函数表达式(IIFE):通过将代码包装在匿名函数中并立即执行该函数,可以创建一个独立的作用域,使得内部声明的变量在函数外部不可访问

javascript">(function () {var var5 = "var5";console.log(var5); // var5
})();
console.log(var5); // var5 is not defined
ES6以后

在es6以后,官方推出了块级作用域的概念,使用let和const关键字声明的变量具有块级作用域,它们只能在声明的代码块内部访问。
 

javascript">if (true) {let var5 = "var5";console.log(var5); // var5
}
console.log(var5); // var5 is not defined

作用域链
作用域链(Scope Chain)是JS用于解析标识符(变量和函数)的机制,它是由多个嵌套的作用域组成的,它决定了变量和函数的查找顺序。

上面我们说到局部作用域时谈到的Foo3可以访问全局以及当前函数作用域中的标识符这个特点就归功于作用域链这个概念。当访问一个变量时,JS引擎会先从当前作用域开始查找,如果找不到这个名称的标识符则继续向上一级作用域查找,直到找到变量或达到全局作用域为止,如果在全局作用域中仍然找不到,则认为该标识符未定义。

变量提升
变量提升的概念出现在面试题中的频率十分高,对于开发者来说也是不可忽略的重要知识点;在之前的面试题文章中的Q6和Q7两题中就出现了变量提升相关的知识点

基础概念
变量提升是JS在代码执行前将变量和函数声明提升到作用域顶部的行为,它由JavaScript引擎在代码执行前的编译阶段处理。变量提升影响了整个作用域范围内的代码,它允许我们在声明之前使用变量,但是需要注意一点:只有变量声明被提升,赋值不会提升。了解了上述概念后,我们思考下面的代码:

javascript">console.log(var6); // undefined
var var6 = 10;

上面的代码相当于

javascript">var var6;
console.log(var6); // undefined
var6 = 10;

优先级问题

当同一个作用域中同时出现同名的函数和变量时,函数提升的优先级更高,也就是说函数会在变量之上声明,参考下面的代码

javascript">var a = 10;
function a() {}
console.log(a); // 10

可以看成是

javascript">var a; // 函数a
var a; // 变量a
a = function () {};// 使用function声明函数可以看成是声明+赋值
a = 10;
console.log(a); // 10

何以见得?思考以下代码

javascript">function a() {}
var a;
console.log(a); // [Function: a]
javascript">当把a的赋值去除时,函数的赋值顺序就可以得到验证,相当于:
javascript">var a; // 函数a
var a; // 变量a
a = function () {};
console.log(a); // [Function: a]

闭包
定义
在介绍垃圾回收和内存变化的文章时,我们提到了堆栈内存管理的原理。当函数开始执行时,函数中的变量以及函数会压入栈中,那么此时如果当前的作用域中有另一个函数正在使用该作用域的变量,该变量占用的内存也不会被垃圾回收机制回收,这个现象就是闭包

换句话说,闭包是指函数能够"记住"并访问其创建时的词法环境,在函数定义的词法作用域之外执行同样适用

思考以下代码
 

javascript">const foo = (function iife() {const num = 10;function foo() {return num;}return foo;
})();
console.log(foo()); // 10

上述代码中使用立即执行函数iife作为外部函数的作用域,它返回内部函数foo,而foo函数使用了iife函数中的num变量,形成了闭包,最后在iife函数的外部使用foo时依然可以访问num变量

特点
即使外部函数已经执行完毕,内部函数依然可以访问外部函数作用域中的变量(当栈将函数弹出时,变量依然处于内存中)
闭包可以持有对外部变量的引用,使得外部变量的值在内部函数中保持活动状态(不被垃圾回收机制回收)
闭包中的内部函数可以修改并更新外部变量的值
闭包的函数可以获取到创建时的整个作用域链的标识符
闭包可能会导致内存泄漏,被闭包引用的变量无法被垃圾回收机制处理
使用场景
封装私有变量
JS中没有TS的private关键词,无法直接定义私有变量,但是可以通过闭包产生私有环境作用域(ES2022后引入了#关键字,用于定义私有变量,相比于使用闭包,更直观和方便)

javascript">const Animal = (function () {const name = "dog";function Animal() {}Animal.prototype.getName = function () {return name;};return Animal;
})();
console.log(new Animal().getName()); // dog
延长变量周期

延长变量的生命周期也是

javascript">function delayMessage(msg) {return function () {return msg;};
}
const msg = delayMessage("msg");
console.log(msg()); // msg

闭包的特性之一,该效果通过内部函数对外部作用域的可访问性实现

javascript">function delayMessage(msg) {return function () {return msg;};
}
const msg = delayMessage("msg");
console.log(msg()); // msg
模块化、命名空间

在TypeScript模块文章中我们使用JS中的立即执行函数实现了命名空间功能,达到模块化的效果

javascript">var moduleA = (function () {var privateVariable = "Hello";// 私有函数function privateMethod() {console.log(privateVariable);}return {// 公共函数publicMethod: function () {privateMethod();},};
})();moduleA.publicMethod(); // Hello
console.log(moduleA.privateVariable); // undefined
缓存

闭包还可以用于创建缓存函数,以提高函数的执行效率。缓存函数可以将输入参数与其对应的结果保存在内部,避免重复计算

javascript">function createCache() {var cache = {};return function (key, val) {if (!cache[key]) {cache[key] = val;console.log("保存");}return cache[key];};
}var cacheModule = createCache();
cacheModule("num1", "123"); // 保存
cacheModule("num2", "456"); // 保存
cacheModule("num1", "123");
cacheModule("num2", "456");

总结
JavaScript作用域与变量提升是编写高质量代码所必须掌握的重要概念。本文介绍了作用域的定义、作用和目的,以及JavaScript中的不同作用域类型,包括全局作用域、函数作用域和块级作用域。我们还讨论了变量提升的概念、原理和影响范围,以及作用域链和闭包的关系。此外,还探讨了ES6的作用域,并对比了其与早期作用域的区别。最后针对动态作用域与词法作用域作了一个简单的说明。

好了,以上就是文章的全部内容了,谢谢你看到了这里,如果觉得文章不错的话,还望三连支持一下,感谢支持!


http://www.ppmy.cn/ops/155140.html

相关文章

LeetCode 2412.完成所有交易的初始最少钱数:【年度巨献】举例说明(讲明白),由难至简(手脚不乱),附Python一行版

【LetMeFly】2412.完成所有交易的初始最少钱数:【年度巨献】举例说明(讲明白),由难至简(手脚不乱),附Python一行版 文章目录 【LetMeFly】2412.完成所有交易的初始最少钱数:【年度巨献】举例说明(讲明白),由难至简(手脚…

MySQL(导入sql文件)

传文件省略…(从windows传到linux) 改编码格式 为什么不在windows里面修改呢?因为windows打开发现根本打不开直接就卡住了数据过多了(4百万数据(不信可以自己试一下)) [rootCentOS8 ~]# file order_info.sql order_info.sql: UTF-8 Unicode…

爬取鲜花网站数据

待爬取网页: 代码: import requestsfrom lxml import etree import pandas as pdfrom lxml import html import xlwturl "https://www.haohua.com/xianhua/"header {"accept":"image/avif,image/webp,image/apng,image/sv…

智能汽车网络安全威胁报告

近年来随着智能汽车技术的快速发展,针对智能汽车的攻击也逐渐从传统的针对单一车辆控制器的攻击转变为针对整车智能化服务的攻击,包括但不限于对远程控制应用程序的操控、云服务的渗透、智能座舱系统的破解以及对第三方应用和智能服务的攻击。随着WP.29 …

Python 梯度下降法(七):Summary

文章目录 Python 梯度下降法(七):Summary一、核心思想1.1 核心思想1.2 优化方法概述1.3 第三方库的使用 二、 BGD2.1 介绍2.2 torch 库算法2.2 代码示例2.3 SGD2.4 SGD代码示例2.5 MBGD2.6 MBGD 代码示例 三、 Adagrad3.1 介绍3.2 torch 库算…

如何使用 DeepSeek API 结合 VSCode 提升开发效率

引言 在当今的软件开发领域,API 的使用已经成为不可或缺的一部分。DeepSeek 是一个强大的 API 平台,提供了丰富的功能和数据,可以帮助开发者快速构建和优化应用程序。而 Visual Studio Code(VSCode)作为一款轻量级但功…

《苍穹外卖》项目学习记录-Day10订单状态定时处理

利用Cron表达式生成器生成Cron表达式 1.处理超时订单 查询订单表把超时的订单查询出来&#xff0c;也就是订单的状态为待付款&#xff0c;下单的时间已经超过了15分钟。 //select * from orders where status ? and order_time < (当前时间 - 15分钟) 遍历集合把数据库…

人工智能入门课【手写自注意力机制】

原理 自注意力&#xff08;Self-Attention&#xff09;是一种强大的机制&#xff0c;广泛应用于自然语言处理、计算机视觉等领域&#xff0c;尤其是在Transformer架构中发挥了关键作用。它的核心思想是让模型能够动态地关注输入序列中不同位置之间的关系&#xff0c;从而更好地…