JavaScript作用域详解

news2024/9/21 10:55:53

目录

前言

什么是作用域

作用域类型

全局作用域

局部作用域

块级作用域

ES6之前

ES6以后

作用域链

变量提升

基础概念

优先级问题

闭包

定义

特点

使用场景

封装私有变量

延长变量周期

模块化、命名空间

缓存

ES6的作用域

const、let

块级作用域

变量提升

暂时性死区

不可重复声明

箭头函数

题外话

动态作用域与词法作用域

词法作用域

动态作用域

总结


前言

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

什么是作用域

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

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

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

作用域类型

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

全局作用域

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

在全局作用域中声明的变量或函数是全局变量或全局函数,在代码任何地方都可以访问和使用

比如上面图示中的Foo4可以访问到全局作用域的变量及函数

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的效果,可以访问到全局以及当前所在函数的作用域的变量及函数

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):通过将代码包装在匿名函数中并立即执行该函数,可以创建一个独立的作用域,使得内部声明的变量在函数外部不可访问

(function () {
  var var5 = "var5";
  console.log(var5); // var5
})();
console.log(var5); // var5 is not defined

ES6以后

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

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引擎在代码执行前的编译阶段处理。变量提升影响了整个作用域范围内的代码,它允许我们在声明之前使用变量,但是需要注意一点:只有变量声明被提升,赋值不会提升。了解了上述概念后,我们思考下面的代码:

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

上面的代码相当于

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

优先级问题

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

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

可以看成是

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

何以见得?思考以下代码

function a() {}
var a;
console.log(a); // [Function: a]

当把a的赋值去除时,函数的赋值顺序就可以得到验证,相当于:

var a; // 函数a
var a; // 变量a
a = function () {};
console.log(a); // [Function: a]

闭包

定义

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

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

思考以下代码

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后引入了#关键字,用于定义私有变量,相比于使用闭包,更直观和方便)

const Animal = (function () {
  const name = "dog";
  function Animal() {}
  Animal.prototype.getName = function () {
    return name;
  };
  return Animal;
})();
console.log(new Animal().getName()); // dog

延长变量周期

延长变量的生命周期也是闭包的特性之一,该效果通过内部函数对外部作用域的可访问性实现

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

模块化、命名空间

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

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

moduleA.publicMethod(); // Hello
console.log(moduleA.privateVariable); // undefined

缓存

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

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");

ES6的作用域

在ES6以后引入了const、let和箭头函数,这三者对作用域分别有什么影响呢?

const、let

块级作用域

上面说到const、let的出现奠定了JS中的块级作用域的概念,使if、for等语句中也存在作用域这一功能;然而不仅仅是条件语句、循环语句,使用{...}定义的范围都是块级作用域,这意味着在块级作用域内部声明的变量只在该作用域内有效,并且在作用域外部无法访问

{
  let msg = "hello";
  console.log(msg); // hello
}
console.log(msg); // msg is not defined

变量提升

使用let和const声明的变量不会被提升到作用域的顶部,它们只能在声明后才能被访问;这点与var不太一样

暂时性死区

暂时性死区指的是在变量声明前访问变量会抛出错误。只有在变量声明语句执行完成之后,变量才会进入有效状态,才能被访问和使用。这点效果与上面的变量提升效果一样

不可重复声明

在同一个作用域中不能被重复声明,否则会报错;而使用var定义变量时后声明的会覆盖先声明的

这三个特点可以参考下面的代码:

console.log(var1); // 在赋值前使用了变量“var1”
const var1 = 11;
let var2 = 22; // 无法重新声明块范围变量“var2”
let var2 = 22;

箭头函数

箭头函数在JavaScript中仍然与普通函数一样有函数作用域的概念

题外话

动态作用域与词法作用域

作用域的种类有两种:分别是动态作用域和词法作用域(静态作用域)。上述我们介绍的是词法作用域,什么是词法作用域?

词法作用域

词法作用域是基于代码的静态结构来确定变量的访问规则。也就是说它由变量和函数在代码中的声明位置而不是调用的位置来确定,思考下面的代码

function foo1() {
  var var1 = 10; // foo2可访问的作用域
  // 声明foo2的作用域
  return function foo2() {
    console.log(var1);// 10
  };
}
var foo2 = foo1();
function foo3() {
  var var1 = 20; // foo2不可访问的作用域
  // 执行foo2的作用域
  foo2();
}
foo3();

在foo1中声明了函数foo2,在foo3中执行foo2,可以看到,foo2取的var1是声明foo2的作用域中的变量(10)。这个现象说明JS采用的是词法作用域。

动态作用域

那么反之,如果还是上述代码,foo2取的var1是执行foo2的作用域中的变量(20),就说明语言采用的是动态作用域

总结

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/815605.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[css]margin-top不起作用问题(外边距合并)

在初学css时&#xff0c;会遇到突然间margin-top不起作用的情况。如下面&#xff1a; 情况一&#xff1a; 代码&#xff1a; <html> <head><style type"text/css"> * {margin:0;padding:0;border:0; }#outer {width:300px;height:300px;backgroun…

IPSec配置简介

实验拓扑&#xff1a; 实验目的 &#xff1a; PC1 ping通 PC2 AR1 配置 静态路由 IP route-static 0.0.0.0 0 100.1.12.2 AR3 配置静态路由 IP route-static 0.0.0.0 0 100.1.13.2 AR1 &#xff1a; 一.配置网络密钥交换提议 ike proposal 1 设置身份认证算法为&…

湖北安全员ABC证书电子版哪里下载?安全员证书可以全国通用吗?

湖北安全员ABC目前一个月有一到两批次的考试&#xff0c;需要报考安全员的直接问甘建二就行了&#xff0c;她都可以给您解疑答惑。 湖北安全员ABC证书电子版证书在哪里下载&#xff1f;怎么下载&#xff1f; 湖北安全员ABC证书也都是电子版的证书&#xff0c;直接从网上下载的…

IBM Spectrum Computing 分布式计算解决方案

IBM Spectrum Computing 分布式计算解决方案 Spectrum Computing 概览 20&#xff0b;年的分布式计算行业经验&#xff0c;全球各行业经验的总结和锤炼;全球TOP500企业客户的选择;前瞻性的产品研发路线&#xff0c;定义企业级分布式数据中心架构;成熟的全球ISV合作伙伴生态保…

为什么VPS是中小型企业的理想选择?

对于中小型企业来说&#xff0c;选择适合自身业务需求的托管方案至关重要。在如今数字化时代&#xff0c;VPS作为一种灵活、高性能的托管解决方案&#xff0c;成为中小型企业的理想选择。作为动态VPS代理产品供应商&#xff0c;我们深知一个高质量、高性能的VPS托管服务&#x…

Linux笔记——Linux基础终端命令

系列文章目录 Linux笔记——搜索命令find、解压缩命令、vi编辑器、用户权限命令、系统信息相关命令讲解 Linux笔记——进程管理与网络监控技术讲解 Linux笔记——磁盘进行分区与挂载介绍_linux 挂载分区 Linux笔记——管道相关命令以及shell编程 Linux笔记——rpm与yum下载…

pdf怎么压缩到1m?pdf压缩跟我这样做

在日常工作和学习中&#xff0c;我们常需要处理PDF文件&#xff0c;然而&#xff0c;有时候&#xff0c;PDF文件的大小可能会超过我们的预期&#xff0c;从而导致我们无法通过电子邮件或其他方式发送。这时候&#xff0c;我们就需要对PDF文件进行压缩&#xff0c;以降低其大小。…

vue-router 实现路由懒加载( 动态加载路由 )

在Vue.js中&#xff0c;可以使用vue-router来实现路由懒加载&#xff0c;也称为动态加载路由。路由懒加载的目的是在用户访问某个路由时再加载对应的组件&#xff0c;而不是在应用初始化时一次性加载所有组件。这样可以提高应用的初始加载速度&#xff0c;只在需要时才加载对应…

企业游学进华秋,助力电子产业创新与发展

近日&#xff0c;淘IC企业游学活动&#xff0c;携20多位电子行业的企业家&#xff0c;走进了深圳华秋电子有限公司&#xff08;以下简称“华秋”&#xff09;&#xff0c;进行交流学习、供需对接。华秋董事长兼CEO陈遂佰对华秋的发展历程、业务版块、产业布局等做了详尽的介绍&…

Maven 打包项目后,接口识别中文乱码

背景 项目在Idea里面运行&#xff0c;调用接口发送中文消息正常&#xff0c;用Maven打包项目后&#xff0c;运行jar包&#xff0c;调用接口发送中文出现乱码。 解决方法 1.Idea编译配置 2.如果更改了上述配置之后还是没有效果&#xff0c;则在运行jar包的前面加上 -Dfile.en…

IDEA Debug小技巧 添加减少所查看变量、查看不同线程

问题 IDEA的Debug肯定都用过。它下面显示的变量&#xff0c;有什么门道&#xff1f;可以增加变量、查看线程吗&#xff1f; 答案是&#xff1a;可以。 演示代码 代码如下&#xff1a; package cn.itcast.attempt.threadAttempt.attempt2;public class Test {public static …

码力全开!请查收HDC.Together 2023亮点日程

<主题演讲> <技术交流与互动> <妙趣之旅> 点击关注阅读原文&#xff0c;了解更多资讯

Debian/Ubuntu 安装 Chrome 和 Chrome Driver 并使用 selenium 自动化测试

截至目前&#xff0c;Chrome 仍是最好用的浏览器&#xff0c;没有之一。Chrome 不仅是日常使用的利器&#xff0c;通过 Chrome Driver 驱动和 selenium 等工具包&#xff0c;在执行自动任务中也是一绝。相信大家对 selenium 在 Windows 的配置使用已经有所了解了&#xff0c;下…

揭开高级产品经理思维的秘密

我经常被问到产品经理如何晋升到更高级别。事实上&#xff0c;获得晋升往往是一场复杂的游戏。是的&#xff0c;你的技能和成就很重要&#xff0c;但其他因素也很重要&#xff0c;比如你的经理对人才培养的关心程度、你的同事有多优秀、任期有多长、公司的政治氛围如何等等。 所…

大学生竞赛管理系统springboot比赛报名信息java jsp源代码mysql

本项目为前几天收费帮学妹做的一个项目&#xff0c;Java EE JSP项目&#xff0c;在工作环境中基本使用不到&#xff0c;但是很多学校把这个当作编程入门的项目来做&#xff0c;故分享出本项目供初学者参考。 一、项目描述 大学生竞赛管理系统springboot 系统有3权限&#xff…

小研究 - 主动式微服务细粒度弹性缩放算法研究(三)

微服务架构已成为云数据中心的基本服务架构。但目前关于微服务系统弹性缩放的研究大多是基于服务或实例级别的水平缩放&#xff0c;忽略了能够充分利用单台服务器资源的细粒度垂直缩放&#xff0c;从而导致资源浪费。为此&#xff0c;本文设计了主动式微服务细粒度弹性缩放算法…

工业RFID读写器型号对比!

工业RFID读写器根据其使用用途可以分为几种&#xff0c;分别是一体式读写器、分体式读写器、平板式读写器以及工业手持终端等。本文将从这几种产品的特点和功能出发&#xff0c;对这几款工业读写器型号进行对比。 工业RFID读写器型号对比 1、一体式读写器 一体式工业读写器的最…

Java并发系列之一:JVM线程模型

什么是线程模型&#xff1a; Java字节码运行在JVM中&#xff0c;JVM运行在各个操作系统上。所以当JVM想要进行线程创建回收这种操作时&#xff0c;势必需要调用操作系统的相关接口。也就是说&#xff0c;JVM线程与操作系统线程之间存在着某种映射关系&#xff0c;这两种不同维…

【Golang 接口自动化07】struct转map的三种方式

目录 背景 struct转map 使用json模块 使用reflect模块 使用第三方库 测试 总结 资料获取方法 背景 我们在前面介绍过怎么使用net/http发送json或者map数据&#xff0c;那么它能不能直接发送结构体数据呢&#xff1f;我们今天一起来学习结构体struct转map的三种方法&am…

(笔记)Layout知识点汇总(积累量变)

Layout知识点汇总 布线1、电容电阻中间不要穿线2、线宽不要超过焊盘&#xff0c;引出后加粗 拐角1、layout&#xff1a;钝角走线 线宽间距1、注意和差分信号线的距离 焊盘1、焊盘中心出线2、线连接到焊盘中心 布局1、时钟线包地处理2、音频的左右声道&#xff0c;加粗&#xff…