前言
大家好,我是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 中四个作用域中的两个来分离我们的代码,使代码的某些部分能够访问某些变量。这是不同作用域级别存在的主要原因,所以我们现在来介绍一下这些不同的作用域。
二、作用域级别
四个不同的作用域界级别分别是:
- 全局作用域(Global Scope)
- 模块作用域(Module Scope)
- 块级作用域(Block Scope)
- 函数作用域(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 中的四个不同的作用域看起来很混乱,但实际上比这要简单一些,因为我们真正关心的只是这四个作用域中的两个。了解这些作用域是如何工作的,对于编写好的干净的代码是非常重要的。
每文一句:星星使天空绚烂夺目;知识使人增长才干。
本次的分享就到这里,如果本章内容对你有所帮助的话欢迎点赞+收藏。文章有不对的地方欢迎指出,有任何疑问都可以在评论区留言。希望大家都能够有所收获,大家一起探讨、进步!