关于 JavaScript 四种作用域的深入探讨

前言

大家好,我是CoderBin。如果你写过哪怕是一行JavaScript代码,那么你就已经在不知不觉中使用了 JavaScript 的四个作用域中的一个。

这些不同的作用域级别决定了你的代码将如何运行,你的代码有多容易编写/更改,以及关于代码中的许多其他因素,所以了解每个不同作用域的所有细微差别是至关重要的。在本篇文章中,我会详细说明每个作用域级别是什么,它们是如何与代码互动的,以及你可以用这些知识来写出更好更干净的代码。

一、什么是作用域?

我们需要解决的第一个问题是什么是作用域。在 JavaScript 中,或者几乎所有其他的编程语言中,代码都在一些设定的范围内运行执行。这个范围决定了你的代码可以访问哪些变量、新的变量将如何与你的代码的其他部分交互,以及其他一些事情。思考作用域的最好方式是作为一个分区,帮助你将代码的不同部分彼此分开。为了更好的理解作用域,有如下代码:

js 复制代码
const outer = 'Out'

function test() {
  const inner = 'In'
  console.log(outer, inner)
  // Prints: "Out", "In"
}

test()
console.log(outer, inner)
// ReferenceError: inner is not defined

在上面的例子中,我们有一些简单的代码,在一个函数外面和一个函数里面定义了一个变量。然后我们从函数内部和外部将两个变量输出到控制台。这在函数内部可以正常工作,但在函数外部,我们的控制台却出现了错误,因为我们不能在函数的范围之外访问内部变量

这个例子利用了 JavaScript 中四个作用域中的两个来分离我们的代码,使代码的某些部分能够访问某些变量。这是不同作用域级别存在的主要原因,所以我们现在来介绍一下这些不同的作用域。

二、作用域级别

四个不同的作用域界级别分别是:

  1. 全局作用域(Global Scope)
  2. 模块作用域(Module Scope)
  3. 块级作用域(Block Scope)
  4. 函数作用域(Function Scope)

这看起来似乎有很多东西要记,但实际上你写的所有代码中95%都会使用模块和块级作用域,所以记起来会容易一些。但这并不意味着你可以忽视其他作用域,因为了解它们的工作原理很重要。

全局作用域(Global Scope)

在开始之前,我们将讨论最简单的作用域,这是大多数开发者在开始时用于所有代码的作用域,因为它有点像忽略了其他作用域的所有限制。为了理解全局作用域,假设我有以下 HTML/JS。

html 复制代码
<script src="script.js"></script>
arduino 复制代码
// script.js

const a = 1
console.log(a)
// 输出: 1

这个简单的例子是用全局作用域来定义变量的。全局范围的工作原理是,当你在文件的顶层定义一个变量时(在任何函数/大括号之外),它被认为是全局作用域,可以在整个程序中的任何地方被访问。这种全局访问在一开始使编写代码变得更容易,因为你不必担心变量被不同的作用域所阻挡,但当你开始编写更复杂的代码时,这很快就变得难以管理。当你有多个文件时,这个问题就更严重了。例如:

html 复制代码
<script src="script-1.js"></script>
<script src="script-2.js"></script>
arduino 复制代码
// script-1.js

const a = 1
arduino 复制代码
// script-2.js

console.log(a)
// 输出: 1

正如上面的例子中看到的,我们是在 script-1.js 中定义了一个变量,但却能够在 script-2.js 中使用该变量。这是因为 script-1.js 中的变量是全局定义的,可以在代码中的任何地方使用。由于全局作用域的这个特殊特性,我尽量不在我的任何应用程序中使用全局作用域。随着应用程序的发展,跟踪全局作用域是非常困难的,这就是为什么我更喜欢模块作用域。

模块作用域(Module Scope)

模块作用域与全局作用域非常相似,但有一个小小的区别。小范围变量只在你定义的文件中可用。这意味着它们不能在其他文件中使用,这在试图跟踪所有东西时是很理想的。为了进入模块作用域,你需要在你的脚本标签上使用 type="module"

html 复制代码
<script src="script-1.js" type="module"></script>
<script src="script-2.js" type="module"></script>
shell 复制代码
// script-1.js

const a = 1
console.log(a)
// 输出: 1
shell 复制代码
// script-2.js

console.log(a)
// ReferenceError: a is not defined

通过这一个改变,我们消除了全局作用域下的变量在每个文件中都可用的问题,同时保留了能够在文件中任何地方使用该变量的好处。正因为如此,模块作用域是我一直在使用的两个作用域之一。

块级作用域(Block Scope)

我一直使用的另一个主要作用域是块作用域 。块作用域是最容易理解的作用域之一,因为它与大括号 {} 相一致。基本上,只要你有大括号内的代码,就被认为是它自己的块作用域。这意味着像函数、if 语句、for 循环等都会产生自己的块级作用域。例如:

js 复制代码
function test() {
  const funcVar = 'Func'

  if (true) {
    const ifVar = 'If'
    console.log(funcVar, ifVar)
    // 输出: "Func", "If"
  }

  console.log(funVar, ifVar)
  // ReferenceError: funVar is not defined
}

这个例子有两个独立的块级作用域被定义。每个块级作用域被定义为大括号之间的代码,每个块都可以访问大括号之间的所有变量,但不是在另一组大括号内,以及其父作用域的所有变量。

在上面的例子中,我们有两个块级作用域分别是 test 函数和 if 语句。其中 test作用域可以访问 test 函数中没有嵌套在另一组大括号中的所有变量,这意味着它可以访问 funcVar 变量。 test 作用域也可以访问任何全局/模块作用域级别的变量,因为它们是 test 作用域的父级。

例子中的第二个作用域是 if 语句,该作用域可以访问 ifVar 以及 test 作用域可以访问的一切,因为 test 作用域是 if 作用域的父作用域。

注意:只要你有大括号,你就会创建一个新的块作用域,该作用域可以访问它所在的所有作用域,但不包括它里面的作用域。这个内部/外部规则实际上对所有作用域都是有效的,外部作用域无法访问到内部作用域的变量。

例如下面这段代码,外部访问了块级作用域内部中的变量 a,就会报错。

js 复制代码
{
  const a = 10
}

console.log(a)
// ReferenceError: a is not defined

函数作用域(Function Scope)

最后一个作用域是函数作用域,它与 var 关键字有关。用 var 关键字定义的变量的作用域是在函数级而不是在块级,这意味着它们只关心函数的大括号。

js 复制代码
function test() {
  var funcVar = 'Func'

  if (true) {
    var ifVar = 'If'
    console.log(funcVar, ifVar)
    // 输出: "Func", "If"
  }

  console.log(funcVar, ifVar)
  // 输出: "Func", "If"
}

这段代码与块级例子中的代码完全相同,但我们使用 var 而不是 const 来定义变量。你会看到在这个例子中,代码可以正常工作,这是因为 var 关键字忽略了块级作用域,所以即使 ifVar 是在 if 块中定义的,对于函数作用域也没有关系。关于声明关键词可前往: # 面试官:你说说var、let、const三者的区别

三、同名的多个变量

关于这个作用域,需要了解的是当有多个同名的变量时,它是如何工作的。

js 复制代码
function test() {
  const a = 'Func'

  if (true) {
    const a = 'If'
    console.log(a)
    // 输出: "If"
  }

  console.log(a)
  // 输出: "Func"
}

在这个例子中,我们在 test 函数和 if 块中都定义了变量 a。当在 if 块中输出 a 时,得到的是 if 作用域中的 a 值,而当我们在 if 块外输出 a 时,得到的是 test 范围中的 a 值。

从这段代码中能认识到,当有两个同名的变量在不同的作用域时,它们实际上是两个完全独立的变量。它们之间没有任何关系,也不会覆盖对方的值,它们的工作方式与两个不同名字的变量完全一样。唯一的区别是,如果它们有相同的名字,那么内部就不可能访问外部作用域变量的值,因为访问 a 总是会访问最内部范围的变量。

所以,一般在写代码时尽量避免使用相同的变量名。

四、总结

虽然 JavaScript 中的四个不同的作用域看起来很混乱,但实际上比这要简单一些,因为我们真正关心的只是这四个作用域中的两个。了解这些作用域是如何工作的,对于编写好的干净的代码是非常重要的。


每文一句:星星使天空绚烂夺目;知识使人增长才干。

本次的分享就到这里,如果本章内容对你有所帮助的话欢迎点赞+收藏。文章有不对的地方欢迎指出,有任何疑问都可以在评论区留言。希望大家都能够有所收获,大家一起探讨、进步!

相关推荐
强强学习2 小时前
HTML5 起步
前端·html·html5
念九_ysl3 小时前
前端循环全解析:JS/ES/TS 循环写法与实战示例
前端·javascript·typescript
anyup_前端梦工厂5 小时前
了解几个 HTML 标签属性,实现优化页面加载性能
前端·html
前端御书房6 小时前
前端PDF转图片技术调研实战指南:从踩坑到高可用方案的深度解析
前端·javascript
2301_789169546 小时前
angular中使用animation.css实现翻转展示卡片正反两面效果
前端·css·angular.js
风口上的猪20157 小时前
thingboard告警信息格式美化
java·服务器·前端
程序员黄同学7 小时前
请谈谈 Vue 中的响应式原理,如何实现?
前端·javascript·vue.js
张胤尘8 小时前
C/C++ | 每日一练 (2)
c语言·c++·面试
爱编程的小庄8 小时前
web网络安全:SQL 注入攻击
前端·sql·web安全
宁波阿成8 小时前
vue3里组件的v-model:value与v-model的区别
前端·javascript·vue.js