前端JavaScript作用域详解

news2024/11/17 3:56:05

目录

前言

什么是作用域

作用域类型

全局作用域

局部作用域

块级作用域

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/814586.html

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

相关文章

tinymce4/5实现将word中内容(文字图片等)直接粘贴至编辑器中——利用插件tinymce-powerpaste-plugin

TinyMCE是一款易用、且功能强大的所见即所得的富文本编辑器。同类程序有:UEditor、Kindeditor、Simditor、CKEditor、wangEditor、Suneditor、froala等等。 TinyMCE的优势: 开源可商用,基于LGPL2.1 插件丰富,自带插件基本涵盖日常…

关于时序图

时序图 01 什么是时序图?02 时序图的组成元素2.1 对象2.2 生命线2.3 消息 03 如何绘制 01 什么是时序图? 时序图是UML交互图中的一类,又名序列图、顺序图。 用于描述对象之间的传递消息的时间顺序(包括发送消息、接收消息、处理…

一个女程序员的成长之路

2013年大学毕业了,带着迷茫与好玩,我还年轻的心态,开始在郑州寻觅工作机会,最后很荣幸的在一家小公司入职了,工作的内容是给种植大棚的用户打电话,推销农药。每天就是在网上各种农业平台上面找号码&#xf…

ASIC-WORLD Verilog(11)过程时序控制

写在前面 在自己准备写一些简单的verilog教程之前,参考了许多资料----Asic-World网站的这套verilog教程即是其一。这套教程写得极好,奈何没有中文,在下只好斗胆翻译过来(加了自己的理解)分享给大家。 这是网站原文&…

【vue】vue中Mixins的用法(jeecg-boot为例):

文章目录 一、jeecg-boot本身只有JeecgListMixin.js二、使用Mixin:三、mixins详解:【1】由于每个项目的接口和参数不同>这里引进js进行处理,不在Mixin里面处理了(Mixin只做公共数据处理)【2】公共的页面字典【3】解决方法里面不…

2009年上半年 软件设计师 上午试卷3

●下图属于UML 中的(46),其中,AccountManagement 需要(47)。 (46)A.组件图 B.部署图 C.类图 D.对象图 (47)A.实现 IdentityVerifier 接口并被 CreditCardServices 调用 B.调用 CreditCardServices 实现的 Identity Verifier 接口 C.实现 I…

设计模式大白话——装饰者模式

装饰者模式 文章目录 装饰者模式一、概述二、应用场景三、代码示例四、小结 一、概述 ​ 装饰者模式,此模式最核心之处在于装饰二字,之所以需要装饰,是因为基础的功能无法满足需求,并且装饰是临时的,并不是永久的&…

基于Java+spring+springMvc+mybatis+jsp学生选课管理系统

基于JavaspringspringMvcmybatisjsp学生选课管理系统 一、系统介绍二、功能展示1.课程列表(学生)2.已选课程(学生)3.已修课程(学生)4.我的课程(老师)5.课程打分(老师)6.课程管理、学生管理、教师管理(系统管理员&#…

python字典:怎么取出key对应的值

目录 python中的字典是什么 怎么判断key是否在字典中 怎么取出key对应的值 总结 python中的字典是什么 在Python中,字典(Dictionary)是一种无序且可变的数据类型,用于存储键-值(Key-Value)对。字典通过…

电脑卡顿反应慢怎么处理?提升反应速度的方法

电脑卡顿反应慢是很常见的问题,然而,我们可以采取一些方法来处理这个问题,帮助大家提升电脑反应速度。​ 一、提升电脑反应速度的方法 当电脑运行顺畅时,我们的工作体验也会更加愉悦。然而,如果电脑出现卡顿反应慢的…

【项目设计】MySQL 连接池的设计

目录 👉关键技术点👈👉项目背景👈👉连接池功能点介绍👈👉MySQL Server 参数介绍👈👉功能实现设计👈👉开发平台选型👈👉MyS…

【雕爷学编程】MicroPython动手做(24)——掌控板之拓展掌控宝

知识点:什么是掌控板? 掌控板是一块普及STEAM创客教育、人工智能教育、机器人编程教育的开源智能硬件。它集成ESP-32高性能双核芯片,支持WiFi和蓝牙双模通信,可作为物联网节点,实现物联网应用。同时掌控板上集成了OLED…

【雕爷学编程】MicroPython动手做(23)——掌控板之WiFi与蓝牙2

知识点:什么是掌控板? 掌控板是一块普及STEAM创客教育、人工智能教育、机器人编程教育的开源智能硬件。它集成ESP-32高性能双核芯片,支持WiFi和蓝牙双模通信,可作为物联网节点,实现物联网应用。同时掌控板上集成了OLED…

1400*D. Candy Box (easy version)(贪心)

3 10 9 Example input 3 8 1 4 8 4 5 6 3 8 16 2 1 3 3 4 3 4 4 1 3 2 2 2 4 1 1 9 2 2 4 4 4 7 7 7 7 output 题意: n个糖果,分为多个种类,要求尽可能的多选,并且使得不同种类的数量不能相同。 解析: 记录每种糖…

音视频技术开发周刊 | 304

每周一期,纵览音视频技术领域的干货。 新闻投稿:contributelivevideostack.com。 更强的Llama 2开源,可直接商用:一夜之间,大模型格局变了 Meta 终于发布了大家期待已久的免费可商用版本 Llama 2。 6000份问卷透露出AI…

Java生成二维码——附Utils工具类

参加2023年的计算机设计大赛国赛,拿到了一等奖。 现在将项目中的工具类代码剥离出来,方便之后项目开发中复用。 实现效果: 代码实现: import com.google.zxing.BarcodeFormat; import com.google.zxing.EncodeHintType; import c…

挑战双面侧柱碰试验:比亚迪CTB保障高品质出行

对于用户来说,选择汽车时最应该重视的要素是什么? 第一,是安全;第二,是安全;第三,还是安全! 那么作为新能源汽车的代表,比亚迪在保障驾乘人员的安全方面又是怎样做的呢&a…

基于SpringCloud+Vue的分布式架构网上商城系统设计与实现(源码+LW+部署文档等)

博主介绍: 大家好,我是一名在Java圈混迹十余年的程序员,精通Java编程语言,同时也熟练掌握微信小程序、Python和Android等技术,能够为大家提供全方位的技术支持和交流。 我擅长在JavaWeb、SSH、SSM、SpringBoot等框架…

FPGA实现NIC 10G UDP协议栈网卡,纯verilog代码编写,提供工程源码和技术支持

目录 1、前言2、我这里已有的UDP方案3、10G网卡基本性能简介4、详细设计方案接口概述PCIe HIPDMA IFAXI总线接口时钟同步处理TXQ和RXQ队列TXCQ和RXCQ队列完成EQ MAC PHY流水线队列管理发送调度程序端口和接口数据路径以及发送和接收引擎分段内存接口 5、vivado工程详解6、上板…

国内 github.com经常打不开的解决办法

1、打开网站http://tool.chinaz.com/dns/ 2、在A类型中填写github.com,再点击监测按钮 3、复制下面任意一个ip 4、打开电脑文件C:\Windows\System32\drivers\etc下的host文件 5、在host文件的最后一刚加入刚才复制的IP 6、重新打开GitHub