了解JavaScript中的变量,范围和提升

更好的编程 (BETTER PROGRAMMING)

Variables are one of the fundamental blocks of any programming language, the way each language defines how we declare and interact with variables can make or break a programming language. Thus any developer needs to understand how to effectively work with variables, their rules, and particularities. In today’s tutorial, we are going to learn how to declare, interact, and scope variables in JavaScript. We will introduce new concepts and important JavaScript keywords like var, let and const.

变量是任何编程语言的基本组成部分之一,每种语言定义我们如何声明和与变量交互的方式都可以构成或破坏一种编程语言。 因此,任何开发人员都需要了解如何有效使用变量,其规则和特殊性。 在今天的教程中,我们将学习如何在JavaScript中声明,交互和作用域变量。 我们将介绍新概念和重要JavaScript关键字,例如varletconst

声明变量 (Declaring variables)

Nowadays JavaScript has three different keywords to declare a variable, var, let and, const. Each with its own properties and particularities. Let’s start by making a simple comparison table of the 3 and then getting into the details.

如今,JavaScript具有三个不同的关键字来声明变量varletconst 。 每个都有自己的属性和特殊性。 让我们开始制作一个简单的3个比较表,然后进入细节。

Image for post

Don’t worry if for now, you are not sure what we mean by scope, hoisting, or any of the other attributes. We are going to cover them in detail next.

暂时不要担心,如果您不确定我们所指的范围,吊装或任何其他属性。 接下来,我们将详细介绍它们。

可变范围 (Variable Scope)

Scope in JavaScript refers to context (or portion) of the code which determines the accessibility (visibility) of variables. In JavaScript, we have 2 types of scope, local and global. Though local scope can have different meanings.

JavaScript中的范围是指代码的上下文(或部分),它确定变量的可访问性(可见性)。 在JavaScript中,我们有两种范围的范围, localglobal 。 虽然本地范围可以具有不同的含义。

Let’s work out through the definitions by giving some examples of how scoping works. Let’s say you define a variable message:

让我们通过定义作用域的一些示例来研究定义。 假设您定义了一个可变message

const message = 'Hello World'
console.log(message) // 'Hello World'

As you may expect the variable message used in the console.log would exist and have the value Hello World. No Doubts there, but what happens if I change a bit where I declare the variable:

如您所料, console.log使用的可变message将存在,并且其值为Hello World 。 毫无疑问,但是如果我在声明变量的位置稍作改动,会发生什么:

if (true) {
const message = 'Hello World'
}
console.log(message) // ReferenceError: message is not defined

Ups… Looks like we broke it, but why? The thing is that the if statement creates a local block scope, and since we used const, the variable is only declared for that block scope, and cannot be accessed from the outside.

大起大落...好像我们把它弄坏了,但是为什么呢? 问题是if语句创建了一个局部块作用域 ,并且由于我们使用了const ,因此仅针对该块作用域声明了该变量,并且不能从外部访问该变量。

Let’s talk a bit more about block and function scopes.

让我们再谈谈块和函数作用域。

区块范围 (Block Scope)

A block is basically a section of code (zero or more statements) which is delimited by a pair of curly braces and may optionally be labeled.

块基本上是一段代码(零个或多个语句),由一对花括号分隔,并且可以选择标记。

As we already discussed the use of let and const allows us to define variables that live within the block scope. Next, we’ll build very similar examples by using different keywords to generate new scopes:

正如我们已经讨论的, letconst的使用允许我们定义存在于块范围内的变量。 接下来,我们将通过使用不同的关键字生成新的范围来构建非常相似的示例:

const x1 = 1
{
const x1 = 2
console.log(x1) // 2
}
console.log(x1) // 1

Let’s explain this one as it may look a bit strange at first. In our outer scope, we are defining the variable x1 with a value of 1. Then we create a new block scope by simply using curly braces, this is strange, but totally legal within JavaScript, and in this new scope, we create a new variable (separate from the one in the outer scope) also named x1. But don’t get confused, this is a brand new variable, which will only be available within that scope.

让我们解释一下这个问题,因为起初看起来可能有些奇怪。 在我们的外部范围中,我们将变量x1定义为1 。 然后,我们仅使用花括号就可以创建一个新的块作用域,这很奇怪,但是在JavaScript中完全合法,在这个新作用域中,我们创建了一个新变量(与外部作用域的变量分开),也称为x1 。 但是请不要感到困惑,这是一个全新的变量,仅在该范围内可用。

Same example now with a named scope:

现在相同的示例具有命名范围:

const x2 = 1
myNewScope: { // Named scope
const x2 = 2
console.log(x2) // 2
}
console.log(x2) // 1

While example (DO NOT RUN THE CODE BELOW!!!!!!!!!!!!!!!!)

而示例( 请勿在下面运行代码!!!!!!!!!! )

const x3 = 1
while(x3 === 1) {
const x3 = 2
console.log(x3) // 2
}
console.log(x3) // Never executed

Can you guess what’s wrong with that code? And what would happen if you run it?… Let me explain, x3 as declared in the outer scope is used for the while comparison x3 === 1, normally inside the while statement, I’d be able to reassign x3 a new value and exit the loop, however as we are declaring a new x3 withing the block scope, we cannot change x3 from the outer scope anymore, and thus the while condition will always evaluate to true producing an infinite loop that will hang your browser, or if you are using a terminal to run it on NodeJS will print a lot of 2.

您能猜出该代码有什么问题吗? 那如果运行它会发生什么呢?…让我解释一下,在外部作用域中声明的x3用于while比较x3 === 1 ,通常在while语句内部,我可以为x3重新分配一个新值并退出循环,但是当我们在块作用域中声明一个新的x3时,就无法再从外部作用域更改x3了,因此while条件将始终为true ,从而产生一个无限循环,该循环会使您的浏览器挂起,或者您使用终端在NodeJS上运行它将打印很多2

Fixing this particular code could be tricky unless you actually rename either variables.

除非您实际上重命名两个变量,否则修复此特定代码可能会很棘手。

So far in our example, we used const, but exactly the same behavior would happen with let. However, we saw in our comparison table that the keyword var is actually function scope, so what does it mean for our examples? Well… let’s take a look:

到目前为止,在我们的示例中,我们使用了const ,但是let会发生完全相同的行为。 但是,我们在比较表中看到关键字var实际上是函数范围,那么它对我们的示例意味着什么? 好吧,让我们看一下:

var x4 = 1
{
var x4 = 2
console.log(x4) // 2
}
console.log(x4) // 2

Amazing! even though we re-declared x4 inside the scope it changed the value to 2 on the inner scope as well as the outer scope. An this is one of the most important differences between let, const, and var and is usually subject (in one way or another) for interview questions.

惊人! 即使我们在范围内重新声明了x4 ,它也在内部范围和外部范围上将值更改为2 。 这是letconstvar之间最重要的区别之一,通常是面试问题的主题(以一种或另一种方式)。

功能范围 (Function Scope)

A function scope is in a way also a block scope, so let and const would behave the same way they did in our previous examples. However, function scopes also encapsulate variables declared with var. but let’s see that continuing with our xn examples:

函数作用域在某种程度上也是块作用域,因此letconst行为与我们之前的示例相同。 但是,函数作用域还封装了用var声明的变量。 但让我们继续看xn示例:

const or let example:

constlet示例:

const x5 = 1
function myFunction() {
const x5 = 2
console.log(x5) // 2
}
myFunction()
console.log(x5) // 1

Exactly as we expected it, and now with var

完全符合我们的预期,现在使用var

var x6 = 1
function myFunction() {
var x6 = 2
console.log(x6) // 2
}
myFunction()
console.log(x6) // 1

In this scenario, var worked the same way as let and const. Moreover:

在这种情况下,var的工作方式与letconst相同。 此外:

function myFunction() {
var x7 = 1
}
console.log(x7) // ReferenceError: x7 is not defined

As we can see, var declarations only exist within the function they were created in and can’t be accessed from the outside.

如我们所见, var声明仅存在于创建它们的函数中,并且不能从外部访问。

But there’s more to it, as always JS has been evolving, and newer type of scopes has been created.

但是还有更多的东西,因为JS一直在发展,并且创建了新的作用域类型。

模块范围 (Module Scope)

With the introduction of modules in ES6, it was important for variables in a module not to directly affect variables in other modules. Can you imagine a world where importing modules from a library would conflict with your variables? Not even JS is that messy! So by definition modules create their own scope which encapsulates all variables created with var, let or const, similar to the function scope.

随着ES6中模块的引入,使模块中的变量不直接影响其他模块中的变量非常重要。 您能想象一个世界,从库中导入模块会与您的变量发生冲突吗? 甚至JS都不那么凌乱! 因此,根据定义,模块创建自己的作用域,该作用域封装使用varletconst创建的所有变量,类似于函数作用域。

There are ways though that modules provide to export variables so they can be accessed from outside the module, and that I covered already in the article An Intro To JavaScript Modules.

尽管模块提供了一些方法来导出变量,以便可以从模块外部对其进行访问,并且我已经在文章“ JavaScript模块简介”中进行了介绍

So far we talked about different types of local scopes, let’s now dive into global scopes.

到目前为止,我们讨论了不同类型的本地范围,现在让我们进入全球范围。

全球范围 (Global Scope)

A variable defined outside any function, block, or module scope has global scope. Variables in global scope can be accessed from everywhere in the application.

在任何函数,块或模块范围之外定义的变量具有全局范围。 可以从应用程序中的任何位置访问全局范围内的变量。

The global scope can sometimes be confused with module scope, but this is not the case, a global scope variable can be used across modules, though this is considered a bad practice, and for good reasons.

有时可以将全局作用域与模块作用域混淆,但事实并非如此,可以在各个模块之间使用全局作用域变量,尽管这是一种不好的做法,并且有充分的理由。

How would you go about declaring a global variable? It depends on the context, it is different on a browser than a NodeJS application. In the context of the browser, you can do something as simply as:

您将如何声明全局变量? 它取决于上下文,在浏览器上与NodeJS应用程序不同。 在浏览器的上下文中,您可以执行以下操作:

<script>
let MESSAGE = 'Hello World'
console.log(MESSAGE)
</script>

Or by using the window object:

或通过使用window对象:

<script>
window.MESSAGE = 'Hello World'
console.log(MESSAGE)
</script>

There are some reasons you wanna do something like this, however, always be careful when you do it.

您出于某些原因想要执行此操作,但是在执行操作时请务必小心。

嵌套范围 (Nesting scopes)

As you probably guessed by now, it is possible to nest scopes, meaning to create a scope within another scope, and its a very common practice. Simply by adding an if statement inside a function we are doing this. So let’s see an example:

您可能已经猜到了,可以嵌套作用域,这意味着可以在另一个作用域中创建作用域,这是一种非常常见的做法。 只需在函数内添加一个if语句,我们就可以做到这一点。 因此,让我们看一个例子:

function nextedScopes() {
const message = 'Hello World!' if (true) {
const fromIf = 'Hello If Block!'
console.log(message) // Hello World!
} console.log(fromIf) // ReferenceError: fromIf is not defined
}nextedScopes()

词汇范围 (Lexical Scope)

In a way, we already made use of lexical scope, though we didn’t know about it. Lexical scope simply means that the children scopes have access to the variables defined in outer scopes.

在某种程度上,我们已经利用了词法作用域,尽管我们对此并不了解。 词法作用域仅表示子作用域可以访问外部作用域中定义的变量。

Let’s see it with an example:

让我们看一个例子:

function outerScope() {
var name = 'Juan'
function innerScope() {
console.log(name) // 'Juan'
} return innerScope
}const inner = outerScope()
inner()

That looks stranger than what it is, so let’s explain it. The function outerScope declares a variable name with value Juan and a function named innerScope. The later does not declare any variables for its own scope but makes use of the variable name declared in the outer function scope.

这看起来比实际情况更奇怪,所以让我们对其进行解释。 函数outerScope声明一个值为Juan的变量name和一个名为innerScope的函数。 后者不为自己的作用域声明任何变量,而是使用在外部函数作用域中声明的变量name

When outerScope() gets called it returns a reference to the innerScope function, which is later called from the outermost scope. When reading this code for the first time you may be confused as to why innerScope would console.log the value Juan as we are calling it from the global scope, or module scope, where name is not declared.

调用outerScope()时,它返回对innerScope函数的引用,此引用随后从最innerScope的范围调用。 第一次阅读此代码时,您可能会困惑为什么innerScope会在我们从未声明name的全局范围或模块范围中调用它时, console.log值值Juan

The reason why this works is thanks to JavaScript closures. Closures is a topic of its own and you can read more about it on the MDN docs. I’m planning an article to explain closures on simple terms but is not ready at the time of this writing.

之所以能够奏效,是因为有了JavaScript闭包功能。 闭包本身是一个主题,您可以在MDN文档中阅读有关闭包的更多信息。 我正在计划撰写一篇文章,以简单的术语解释闭包,但在撰写本文时尚未准备好。

吊装 (Hoisting)

Hoisting in terms of JavaScript means that a variable is created in memory during the compile phase, and thus they can actually be used before they are actually declared. Sounds super confusing, let’s better see it in code.

用JavaScript吊起意味着在编译阶段在内存中创建一个变量,因此可以在实际声明它们之前实际使用它们。 听起来超级混乱,让我们更好地在代码中看到它。

This is what a normal flow would look like:

正常流程如下所示:

function displayName(name) {
console.log(name)
}displayName('Juan')//***********************
// Outputs
//***********************
// 'Juan'

Awesome! as expected that works, but what would you think of the following:

太棒了! 可以正常工作,但是您会怎么看:

hoistedDisplayName('Juan')function hoistedDisplayName(name) {
console.log(name)
}//***********************
// Outputs
//***********************
// 'Juan'

Wait wait wait…. what? As crazy as it sounds, since the function is assigned to memory before the code actually runs, the function hoistedDisplayName is available before its actual definition, at least in terms of code lines.

等等等等...。 什么? 听起来很疯狂,由于该函数在代码实际运行之前已分配给内存,因此至少在代码行方面, hoistedDisplayName函数在其实际定义之前可用。

Functions have this particular property, but also do variables declared with var. Let’s see an example:

函数具有此特定属性,但也具有用var声明的变量。 让我们来看一个例子:

console.log(x8) // undefined
var x8 = 'Hello World!'

Not what you guessed? The fact that the variable is “created” before its actual definition in the code doesn’t mean that its value is already assigned, this is why when we do the console.log(x8) we don’t get an error saying that the variable is not declared, but rather the variable has value undefined. Very interesting, but what happens if we use let or const? Remember in our table they don’t share this property.

不是你的猜测吗? 变量在代码中的实际定义之前“创建”的事实并不意味着已经分配了它的值,这就是为什么当我们执行console.log(x8)我们不会收到错误提示,没有声明变量,而是变量具有undefined值。 非常有趣,但是如果使用letconst会发生什么呢? 请记住,在我们的表中他们没有共享此属性。

console.log(x9) // Cannot access 'x9' before initialization
const x9 = 'Hello World!'

It threw an error.

它抛出了一个错误。

Hoisting is a lesser-known property of JavaScript variables, but it’s also an important one. Make sure you understand the differences, it is important for your code, and it may be a topic for an interview question.

提升是JavaScript变量鲜为人知的特性,但它也是重要的特性。 确保您了解差异,这对您的代码很重要,并且可能是面试问题的主题。

重新分配变量 (Reassignment of variables)

This topic covers specifically variables declared with the keyword const. A variable declared with const cannot be reassigned, meaning that we can’t change its value for a new one, but there’s a trick. Let’s see some examples:

本主题专门介绍用关键字const声明的变量。 用const声明的变量无法重新分配,这意味着我们不能为新变量更改其值,但是有一个技巧。 让我们看一些例子:

const c1 = 'hello world!'
c1 = 'Hello World' // TypeError: Assignment to constant variable.

As we expected, we can’t change the value of a constant, or can we?

正如我们期望的那样,我们无法更改常量的值,或者可以吗?

const c2 = { name: 'Juan' }
console.log(c2.name) // 'Juan'
c2.name = 'Gera'
console.log(c2.name) // 'Gera'

Did we just change the value of a const value? The short answer is NO. Our constant c2 references an object with a property name. c2 is a reference to that object, that’s its value. When we do c2.name we are really taking the pointer to the c2 object and accessing the property from there. What we are changing when we do c2.name is the value of the property name in the object, but not the reference stored in c2, and thus c2 remained constant though the property value is now different.

我们是否只是更改了const值的值? 简短的答案是“ 否” 。 我们的常量c2引用一个具有属性name的对象。 c2是对该对象的引用,即其值。 当我们执行c2.name我们实际上是在指向c2对象的指针并从那里访问属性。 当我们执行c2.name时,我们要更改的是对象中属性name的值,而不是存储在c2的引用,因此尽管现在属性值不同,但是c2保持不变。

See what happens when we actually try to update the value differently:

看看当我们实际尝试不同地更新值时会发生什么:

const c3 = { name: 'Juan' }
console.log(c3.name) // 'Juan'
c3 = { name: 'Gera' } // TypeError: Assignment to constant variable.
console.log(c3.name)

Even though the object looks the same, we are actually creating a new object { name: 'Gera' } and trying to assign that new object to c3, but we can’t as it was declared as constant.

即使对象看起来相同,我们实际上仍在创建一个新对象{ name: 'Gera' }并尝试将该新对象分配给c3 ,但是我们不能将其声明为常量。

结论 (Conclusion)

Today I covered the topic of variable declarations and scoping in JavaScript. It is a very important topic that can explain many weird situations that can happen to our code. And that is a usual interview question. It is a must-learn and understand for all JavaScript developers.

今天,我讨论了JavaScript中的变量声明和作用域的主题。 这是一个非常重要的主题,可以解释我们的代码可能发生的许多奇怪情况。 这是一个常见的面试问题。 这是所有JavaScript开发人员必须学习和理解的。

Some time ago I posted an article on 5 JavaScript Questions and Answers to Test Your Skills where 2 of the questions (#4 and #5) are actual interview questions. The whole article is very interesting, but those 2 questions in particular are good examples of how scopes and closures can make a big difference in the results of your code.

前段时间,我在5个JavaScript问答中测试了您的技能 ,其中有2个问题(#4和#5)是实际的面试问题。 整篇文章非常有趣,但是特别是这两个问题是作用域和闭包如何对代码结果产生重大影响的很好的例子。

Thanks so much for reading!

非常感谢您的阅读!

翻译自: https://levelup.gitconnected.com/understanding-variables-scope-and-hoisting-in-javascript-93018bf29190

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值