JavaScript变量定义和作用域的可视化指南(入门级)

997 阅读8分钟

我们常讨论var,let和const之间的作用域的差异。但更多的时候,我看到不少初学者仍然在尝试着理解这个观点。我认为这可能是因为这个概念很少被可视化。 让我们一起来看一看。并不是所有的作用域看起来都是一样的。
注意:我并不建议大家死记作用域变量定义和每种类型的作用域的值可见性规则。相反,你们可以试着找出其运作的实际原因。(例如,变量隐私。)
块级作用域
简单的块级作用域可访问性规则:

全局作用域变量可以进入内部块作用域。使用var定义的块作用域变量被hoisted回全局作用域,但是值没有被传递(只有变量名定义),并且没有被定义。
函数作用域
但是,全局作用域到函数作用域基本上是单向的:

全局变量可以进入函数作用域,但在函数作用域中定义的变量,只能在函数中被访问,而不能进入全局作用域。
闭包模式的提示
函数启用闭包模式,因为它们的变量隐藏在全局作用域之外,但仍可以从其中的其他函数作用域访问:

主要是为了防止变量受全局作用域的影响,但仍然能够从全局作用域调用函数。我们一会儿会更详细地看一下。 从长远来看,保护变量不受外部作用域的影响有助于减少bug。大家可能不能现在就理解它。但是如果坚持这个想法,大家在以后可以避免一些不必要的错误。
局作用域
全局作用域中的无差异

在教程的开头,我们说到了“var、let和const之间有如此多的作用域差异”,所以大家很容易觉得这三个是完全不同的。但事实并非总是如此。 当变量在全局作用域中定义时,var,let和const在作用域可见性方面没有差异。

关键字let和const将变量限制在它们被定义的作用域内:

使用let和const定义的变量不能被声明提升。只有var可以。
在函数作用域里
但是,当涉及到函数作用域时,所有变量类型,包括var,都将保持 仅限于其范围:

我们都不能访问定义这些变量的函数作用域以外的变量, 无论使用哪个关键字。
闭包
函数闭包模式的一部分是一个被困在另一个函数中的函数。内部函数返回counter变量。但是,因为add()函数本身就是闭包容器,所以它从不泄漏到全局范围中。
技巧?counter变量在全局作用域内隐藏。但我们仍然可以从全局作用域调用add()。换句话说,counter变量仍然是隐蔽的,但是我们可以以闭包的返回值从全局作用域中访问它。

的确,这是一个抽象的视觉表现。但是在代码中闭包实际上是什么样子的呢?

总的来说,这意味着我们定义了一个匿名函数并将其作为一个语句进行计算。这相当于我们执行(1).toString()的操作,因为1.toString()会生成一个错误。
(func(){}()中的第二个括号←在一条JavaScript语句中定义完所有内容后立即执行该函数。就是这样。
我们为什么要这样做?
plus()被一个执行自身的匿名函数定义。在plus作用域内,将创建另一个匿名函数。它增加一个秘密变量counter,并将结果返回到全局作用域。
重点:全局作用域在任何时候都不能访问或修改counter变量。闭包模式使其内部函数在不会使变量泄漏到全局作用域的情况下能够做到修改变量,关键是全局作用域不需要知道或了解add()中的代码是如何工作的。它只关心接收add()操作的结果,以便将其传递给其他函数等。
现在闭包不再像以前那样多人用了。它们是在ES6时代以前被创建的。当私有变量被添加到用class关键字创建的定义中时,它们的功能将在将来被替换。
那我为什么还要费心去讲解它呢? 有时这也被称为封装—面向对象编程其中一个关键原则,在这个原则中,类成员方法的内部工作被隐藏在调用它们的环境中。
我们仔细想想就会意识到,这正是let被发明的原因。它为块级作用域中定义的变量提供自动隐私。一般来说,变量隐私是许多编程语言的一个基本特性。
在局部作用域里
let和const关键字隐藏了定义它们的作用域及其内部作用域的可变可见性。
当我们开始在局部块级作用域或函数级作用域内定义变量时,作用域可见性差异就会显现出来。正如我们前面讨论过的,在全局作用域里没有差异。
在类里
类作用域仅仅只是一个占位符。如果我们试图在类作用域里直接定义变量将产生一个错误:

下面是定义局部变量和属性的适当位置。
注意,let(或var或const)只为该作用域创建一个局部变量。因此,不能从类方法访问它。 在类中,变量在其构造函数或方法中定义:

类构造函数中的this关键字
这个this关键字是用于定义对象属性。一旦在构造函数中定义了它们,就可以通过this关键字在所有类方法中访问对象属性(或者更确切地说,是引用对象)。
将this看作该类定义的对象的实例。
巧合:在实例化对象之前,这个this关键字在构造函数中已经出现了,但它只是作为对象分配新属性的一种方式。在方法中使用this意味着对象已经被实例化了。
局部变量的定义
局部变量可以使用var、let或const关键字来定义,但是它们可以保持对构造函数或定义它们的方法的作用域的限制,而不实际成为对象的属性。请牢记,构造函数和方法严格地来说仍然只是函数作用域。
下面是它在代码中的样子:

剖析类:构造函数和方法喵()。我们这里只存在一个创建对象的实例的构造函数。但是我们可以有许多的方法。将方法看作类可以执行的操作。
对猫来说可能是:喵(),吃(),喝(),睡觉()。我们的类应该具有哪些操作呢?这取决于类定义的对象的用途和类型。
这是一个抽象的模型。我们居然是这个类的设计师,这够疯狂吧。
注意,因为对象felix实际上是在全局作用域内创建的,所以我们可以在类方法中访问它(但不能在类构造函数中访问,因为在构造函数中对象的实例仍然在被实例化)。 但是,既然我们可以简单地使用this关键字来指代相同的东西,那么为什么在喵()类中使用felix呢?在这里我们只是举个个例子。
**const ** 这个const关键字不同于let和var。它不允许将先前定义的变量重新赋值给新值:

即使变量是使用const定义的,我们仍然可以更改数组或对象等更复杂的数据结构的值。我们一起来看看!
const和数组
我们仍然可以更改Const数组中的值,只是不能将对象重新分配给不同的数组:

但是我们不能为原始变量名分配新值。一旦这个是数组了它就只能是数组了。或者一旦这个是一个对象它就只能是一个对象了:
常量和对象文本
当涉及到对象文字时,const只让定义保持常量。 但这并不意味着你不能改变分配给const定义的变量的属性值:

你学会了吗?

今天的分享就到这里,希望本文能帮助到您!

点赞,让更多的人也能看到这篇内容(收藏不点赞,都是耍流氓 -_-)
关注公众号「新前端社区」,享受文章首发体验!
每周重点攻克一个前端技术难点。