js执行环境作用域和闭包_JavaScript中执行上下文,提升,作用域和闭包的终极指南

 2023-09-06 阅读 21 评论 0

摘要:js执行环境作用域和闭包It may seem surprising, but in my opinion the most important and fundamental concept to understanding the JavaScript language is understanding Execution Context. By properly learning it, you'll be positioned nicely to learn more

js执行环境作用域和闭包

It may seem surprising, but in my opinion the most important and fundamental concept to understanding the JavaScript language is understanding Execution Context. By properly learning it, you'll be positioned nicely to learn more advanced topics like hoisting, scope chains, and closures. With that in mind, what exactly is an "Execution Context"? To better understand it, let's first take a look at how we write software.

这似乎令人惊讶,但是我认为理解JavaScript语言最重要和最基本的概念是理解执行上下文。 通过正确学习它,您将可以很好地定位于学习更高级的主题,例如提升,作用域链和闭合。 考虑到这一点,“执行上下文”到底是什么? 为了更好地理解它,让我们首先看一下我们如何编写软件。

One strategy for writing software is to break our code up into separate pieces. Though these "pieces" have many different names (functions, modules, packages, etc), they all exist for a single purpose - to break apart and manage the complexity in our applications. Now instead of thinking like someone authoring code, think in terms of the JavaScript engine whose job is to interpret code. Can we use that same strategy, separating code into pieces, to manage the complexity of interpreting code just like we did in order to write it? Turns out we can and these "pieces" are called Execution Contexts. Just like functions/modules/packages allow you to manage the complexity of writing code, Execution Contexts allow the JavaScript engine to manage the complexity of interpreting and running your code. So now that we know the purpose of Execution Contexts, the next questions we need to answer are how do they get created and what do they consist of?

编写软件的一种策略是将我们的代码分成不同的部分。 尽管这些“部分”具有许多不同的名称(功能,模块,软件包等),但它们的存在都是出于一个单一目的-分解并管理应用程序的复杂性。 现在,不再像编写代码的人那样思考,而是以负责解释代码JavaScript引擎来思考。 我们是否可以使用相同的策略将代码分成几部分,来管理解释代码的复杂性,就像编写代码一样? 事实证明,我们可以将这些“片段”称为执行上下文。 就像功能/模块/包允许您管理编写代码的复杂性一样,执行上下文也允许JavaScript引擎管理解释和运行代码的复杂性。 因此,既然我们知道了执行上下文的目的,接下来我们需要回答的问题是如何创建它们以及它们由什么组成?

The first Execution Context that gets created when the JavaScript engine runs your code is called the "Global Execution Context". Initially this Execution Context will consist of two things - a global object and a variable called this. this will reference the global object which will be window if you're running JavaScript in the browser or global if you're running it in a Node environment.

当JavaScript引擎运行您的代码时创建的第一个执行上下文称为“全局执行上下文”。 最初,此执行上下文将由两部分组成-全局对象和称为this的变量。 this将引用全局对象,这将是window ,如果你在浏览器中运行JavaScript或global ,如果你在一个节点环境中运行它。

Above we can see that even without any code, the Global Execution Context will still consist of two things - window and this. This is the Global Execution Context in its most basic form.

在上方我们可以看到,即使没有任何代码,全局执行上下文仍将由两部分组成windowthis 。 这是最基本形式的全局执行上下文。

Let's step things up and see what happens when we start actually adding code to our program. Let's start with adding a few variables.

让我们加紧准备,看看当我们开始向程序中实际添加代码时会发生什么。 让我们从添加一些变量开始。

Can you spot the differences between those two image above? The key take away is that each Execution Context has two separate phases, a Creation phase and an Execution phase and each phase has its own unique responsibilities.

您能发现上面两个图像之间的区别吗? 关键要解决的是,每个执行上下文都有两个单独的阶段,即Creation阶段和Execution阶段,并且每个阶段都有自己的独特职责。

In the Global Creation phase, the JavaScript engine will

在Global Creation阶段,JavaScript引擎将

  1. Create a global object.

    创建一个全局对象。
  2. Create an object called "this".

    创建一个名为“ this”的对象。
  3. Set up memory space for variables and functions.

    设置变量和函数的存储空间。
  4. Assign variable declarations a default value of "undefined" while placing any function declarations in memory.

    将任何函数声明放入内存时,为变量声明分配默认值“ undefined”。

It's not until the Execution phase where the JavaScript engine starts running your code line by line and executing it.

直到Execution阶段,JavaScript引擎才开始逐行运行代码并执行它。

We can see this flow from Creation phase to Execution phase in the GIF below.

我们可以在下面的GIF中看到从Creation阶段到Execution阶段的流程。

During the Creation phase window and this are created, variable declarations (name and handle) are assigned a default value of undefined, and any function declarations (getUser) are placed entirely into memory. Then once we enter the Execution phase, the JavaScript engine starts executing the code line by line and assigns the real values to the variables already living in memory.

Creation阶段windowthis被创建,变量声明( namehandle )被分配的缺省值undefined ,任何函数声明( getUser )置于完全到内存中。 然后,一旦我们进入Execution阶段,JavaScript引擎便开始逐行执行代码,并将实际值分配给已经存在于内存中的变量。

GIFs are cool, but not as cool as stepping through the code and seeing the process for yourself. Because you deserve it, I created JavaScript Visualizer just for you. If you want to walk through the exact code above, use THIS LINK.

GIF很酷,但不像单步执行代码并亲自查看过程那样酷。 因为您应得的,所以我为您创建了JavaScript Visualizer 。 如果您想遍历上面的确切代码,请使用THIS LINK 。

To really cement this idea of Creation phase vs Execution phase, let's log some values after the Creation phase and before the Execution phase.

要真正巩固这一想法的Creation阶段VS Execution阶段,让我们记录一些值 Creation阶段和之前 Execution阶段。

console.log('name: ', name)
console.log('handle: ', handle)
console.log('getUser :', getUser)var name = 'Tyler'
var handle = '@tylermcginnis'function getUser () {return {name: name,handle: handle}
}

In the code above, what do you expect to be logged to the console? By the time the JavaScript engine starts executing our code line by line and invoking our console.logs, the Creation phase has already happened. What that means is that, as we saw earlier, the variable declarations should have been assigned a value of undefined while the function declaration should be fully in memory already. So just as we should expect, name and handle are undefined and getUser is a reference to the function in memory.

在上面的代码中,您希望登录到控制台什么? 当JavaScript引擎开始逐行执行代码并调用console.logs时, Creation阶段已经开始。 正如我们前面所看到的,这意味着应该为变量声明分配一个undefined的值,而函数声明应该已经完全位于内存中。 因此,正如我们期望的那样, namehandle undefined并且getUser是对内存中函数的引用。

console.log('name: ', name) // name: undefined
console.log('handle: ', handle) // handle: undefined
console.log('getUser :', getUser) // getUser: ƒ getUser () {}var name = 'Tyler'
var handle = '@tylermcginnis'function getUser () {return {name: name,handle: handle}
}

This process of assigning variable declarations a default value of undefined during the creation phase is called Hoisting.

在创建阶段为变量声明分配默认值undefined过程称为Hoisting

Hopefully you just had an 'Aha!" moment. You may have had "hoisting" explained to you previously without much success. The thing that's confusing about "hoisting" is that nothing is actually "hoisted" or moved around. Now that you understand Execution Contexts and that variable declarations are assigned a default value of undefined during the Creation phase, you understanding "hoisting" because that's literally all it is.

希望您刚刚经历了一个“啊哈!”的时刻,您可能之前没有向您解释过“吊装”成功,“吊装”令人困惑的是实际上没有任何“吊装”或四处移动。在Creation阶段,执行上下文以及为变量声明分配的默认值均为undefined ,您可以理解“提升”,因为从字面上看,它就是全部。



At this point you should be fairly comfortable with the Global Execution Context and its two phases, Creation and Execution. The good news is there's only one other Execution Context you need to learn and its almost exactly identical to the Global Execution Context. It's called the Function Execution Context and it's created whenever a function is invoked.

在这一点上,您应该对Global Execution Context及其两个阶段CreationExecution相当满意。 好消息是,您只需要学习另一个执行上下文,它几乎与全局执行上下文完全相同。 它称为函数执行上下文,并且在调用函数时创建。

This is key. The only time an Execution Context is created is when the JavaScript engine first starts interpreting your code (Global Execution Context) and whenever a function is invoked.

这是关键。 创建执行上下文的唯一时间是JavaScript引擎首次开始解释您的代码(全局执行上下文)的时间以及每当调用函数时。

Now the main question we need to answer is what's the difference between the Global Execution Context and a Function Execution Context. If you remember from earlier, we said that in the Global Creation phase, the JavaScript engine will

现在,我们需要回答的主要问题是全局执行上下文和函数执行上下文之间的区别是什么。 如果您还记得以前,我们说过,在Global Creation阶段,JavaScript引擎将

  1. Create a global object.

    创建一个全局对象。
  2. Create an object called "this".

    创建一个名为“ this”的对象。
  3. Set up memory space for variables and functions.

    设置变量和函数的存储空间。
  4. Assign variable declarations a default value of "undefined" while placing any function declarations in memory.

    将任何函数声明放入内存时,为变量声明分配默认值“ undefined”。

Which of those steps doesn't make sense when we're talking about a Function Execution Context? It's step #1. We should only ever have one global object that's created during the Creation phase of the Global Execution Context, not every time a function is invoked and the JavaScript engine creates a Function Execution Context. Instead of creating a global object, one thing a Function Execution Context needs to worry about that the Global Execution Context doesn't are arguments. With that in mind, we can adapt our list from earlier. Whenever a Function Execution Context is created, the JavaScript engine will

当我们谈论函数执行上下文时,以下哪几个步骤没有意义? 这是第一步。 我们应该只在全局执行上下文的Creation阶段创建一个全局对象,而不是每次调用一个函数并且JavaScript引擎创建一个函数执行上下文时都应该创建一个全局对象。 除了创建全局对象外,函数执行上下文还需要担心全局执行上下文不是参数。 考虑到这一点,我们可以改写较早的清单。 每当创建函数执行上下文时,JavaScript引擎都会

1. Create a global object.    1. Create an arguments object.    2. Create an object called this.    3. Set up memory space for variables and functions.    4. Assign variable declarations a default value of "undefined" while placing any function declarations in memory.

1. 创建一个全局对象。 1.创建一个参数对象。 2.创建一个名为this的对象。 3.设置变量和函数的存储空间。 4.在将任何函数声明放入内存时,为变量声明分配默认值“ undefined”。

To see this in action, let's go back to the code we had earlier, but this time instead of just defining getUser, let's see what happens when we invoke it.

为了了解这一点,让我们回到之前的代码,但是这次不只是定义getUser ,而是让我们看看调用它时会发生什么。

Visualize the code yourself

自己可视化代码

Just as we talked about, when we invoke getUser a new Execution Context is created. During the Creation phase of getUsers Execution Context, the JavaScript engine creates a this object as well as an arguments object. Because getUser doesn't have any variables, the JavaScript engine doesn't need to set up any memory space or "hoist" any variable declarations.

就像我们刚才谈到的那样,当我们调用getUser时,会创建一个新的Execution Context。 在getUsers执行上下文的Creation阶段,JavaScript引擎会创建一个this对象以及arguments对象。 因为getUser没有任何变量,所以JavaScript引擎不需要设置任何内存空间或“提升”任何变量声明。

You may have also noticed that when the getUser function is finished executing, it's removed from the visualization. In reality, the JavaScript engine creates what's called an "Execution Stack" (also known as the "Call Stack"). Anytime a function is invoked, a new Execution Context is created and added to the Execution Stack. Whenever a function is finished running through both the Creation and Execution phase, it gets popped off the Execution Stack. Because JavaScript is single threaded (meaning only one task can be executed at a time), this is easy to visualize. With "JavaScript Visualizer" the Execution Stack is shown in a nested fashion with each nested item being a new Execution Context on the Execution Stack.

您可能还注意到,当getUser函数执行完后,将从可视化中删除它。 实际上,JavaScript引擎会创建所谓的“执行堆栈”(也称为“调用堆栈”)。 每当调用函数时,都会创建一个新的执行上下文并将其添加到执行堆栈中。 每当函数在CreationExecution阶段均完成运行时,就会从执行堆栈中弹出。 由于JavaScript是单线程的(意味着一次只能执行一个任务),因此很容易看到。 使用“ JavaScript Visualizer”,执行堆栈以嵌套方式显示,每个嵌套项都是执行堆栈上的新执行上下文。

Visualize the code yourself

自己可视化代码



At this point we've seen how function invocations create their own Execution Context which get placed on the Execution Stack. What we haven't seen yet is how local variables play into that. Let's change up our code so our functions have local variables.

至此,我们已经了解了函数调用如何创建自己的执行上下文,并将其放置在执行堆栈中。 我们还没有看到局部变量是如何发挥作用的。 让我们更改代码,使我们的函数具有局部变量。

Visualize the code yourself

自己可视化代码

There are few important details to notice here. First is that any argument you pass in will be added as a local variable in that function's Execution Context. In the example handle exists both as a variable in the Global Execution Context (since that's where it was defined) as well as the getURL Execution Context because we passed it in as an argument. Next is that variables declared inside of a function live inside that function's Execution Context. So when we created twitterURL, it lived inside of the getURL Execution Context since that's where it was defined, not the Global Execution Context. That may seem obvious, but it's fundamental to our next topic, Scopes.

这里没有几个重要的细节需要注意。 首先,您传递的所有参数都将作为该函数的执行上下文中的局部变量添加。 在示例中, handle既作为Global执行上下文中的变量存在(因为它是在此定义的),也存在于getURL执行上下文中,因为我们将其作为参数传入。 接下来是在函数内部声明的变量位于该函数的执行上下文中。 因此,当我们创建twitterURL ,它位于getURL执行上下文内部,因为它是在此处定义的, 而不是 Global执行上下文。 这看起来似乎很明显,但这是我们下一个主题Scope的基础。



In the past you probably heard a definition of "Scope" along the lines of "where variables are accessible". Regardless of whether or not that made sense at the time, with your newfound knowledge of Execution Contexts and the JavaScript Visualizer tool, Scopes will be more clear than they've ever been. In fact, MDN defines "Scope" as "The current context of execution." Sound familiar? We can think of "Scope" or "where variables are accessible" in a very similar way to how we've been thinking about execution contexts.

过去,您可能会听到“范围在哪里可以访问”的“范围”定义。 不管当时是否有意义,借助您对执行上下文和JavaScript Visualizer工具的最新了解,范围将比以往更加清晰。 实际上,MDN将“范围”定义为“当前执行上下文”。 听起来有点熟? 我们可以以与我们一直在考虑执行上下文的方式非常相似的方式来考虑“范围”或“可访问变量”。

Here's a test for you. What will bar be when it's logged in the code below?

这是给您的测试。 在下面的代码中登录后, bar将是什么?

function foo () {var bar = 'Declared in foo'
}foo()console.log(bar)

Let's check it out in JavaScript Visualizer.

让我们在JavaScript Visualizer中检查一下。

Visualize the code yourself

自己可视化代码

When foo is invoked we create a new Execution Context on the Execution Stack. The Creation phase creates this, arguments, and sets bar to undefined. Then the Execution phase happens and assigns the string Declared in foo to bar. After that the Execution phase ends and the foo Execution Context is popped off the stack. Once foo is removed from the Execution Stack, we try to log bar to the console. At that moment, according to JavaScript Visualizer, it's as if bar never even existed so we get undefined. What this shows us is that variables created inside of a function are locally scoped. That means (for the most part, we'll see an exception later) they can't be accessed once the function's Execution Context has been popped off the Execution Stack.

foo被调用时,我们在执行堆栈上创建一个新的执行上下文。 Creation阶段创建this arguments ,并将bar设置为undefined 。 然后Execution阶段发生,并将Declared in foo的字符串分配给bar 。 之后, Execution阶段结束,并且将foo执行上下文从堆栈中弹出。 将foo从执行堆栈中删除后,我们尝试将bar登录到控制台。 根据JavaScript Visualizer的描述,那时好像bar根本不存在,所以我们变得undefined 。 这说明我们在函数内部创建的变量是局部作用域的。 这意味着(在大多数情况下,我们将在以后看到一个异常)一旦函数的执行上下文从执行堆栈中弹出后就无法访问它们。

Here's another. What will be logged to the console after the code is finished executing?

这是另一个 代码完成执行后,将记录到控制台的内容是什么?

function first () {var name = 'Jordyn'console.log(name)
}function second () {var name = 'Jake'console.log(name)
}console.log(name)
var name = 'Tyler'
first()
second()
console.log(name)

Again, let's take a look at JavaScript Visualizer.

再次,让我们看一下JavaScript Visualizer。

Visualize the code yourself

自己可视化代码

We get undefined, Jordyn, Jake, then Tyler. What this shows us is that you can think of each new Execution Context as having its own unique variable environment. Even though there are other Execution Contexts that contain the variable name, the JavaScript engine will first look to the current Execution Context for that variable.

我们得到undefinedJordynJake ,然后是Tyler 。 这说明我们可以将每个新的执行上下文视为具有自己独特的可变环境。 即使还有其他包含变量name执行上下文,JavaScript引擎也会首先查找该变量的当前执行上下文。

This brings up the question, what if the variable doesn't exist in the current Execution Context? Will the JavaScript engine just stop trying to look for that variable? Let's see an example that will answer this question. In the code below, what's going to be logged?

这带来了一个问题,如果当前执行上下文中不存在该变量该怎么办? JavaScript引擎会停止尝试查找该变量吗? 让我们看一个可以回答这个问题的例子。 在下面的代码中,将要记录什么?

var name = 'Tyler'function logName () {console.log(name)
}logName()

Visualize the code yourself

自己可视化代码

Your intuition might be that it's going to log undefined since the logName Execution Context doesn't have a name variable in its scope. That's fair but it's wrong. What happens is if the JavaScript engine can't find a variable local to the function's Execution Context, it'll look to to nearest parent Execution Context for that variable. This lookup chain will continue all the way until the engine reaches the Global Execution Context. In that case, if the Global Execution Context doesn't have the variable, it'll throw a Reference Error.

您的直觉可能是由于logName Execution上下文在其作用域内没有name变量,因此它将记录为undefined 。 公平,但这是错误的。 发生的事情是,如果JavaScript引擎找不到函数的执行上下文本地的变量,它将寻找该变量的最近父执行上下文。 该查找链将一直持续到引擎到达全局执行上下文为止。 在这种情况下,如果Global Execution Context没有该变量,它将抛出“引用错误”。

This process of the JavaScript engine going one by one and checking each individual parent Execution Context if a variable doesn't exist in the local Execution Context is called the Scope Chain. JavaScript Visualizer shows the Scope Chain by having each new Execution Context indented and with a unique colored background. Visually you can see that any child Execution Context can reference any variables located in any of its parent Execution Contexts, but not vice versa.

如果本地执行上下文中不存在变量,JavaScript引擎将一步一步检查每个单独的父执行上下文的过程称为Scope Chain 。 JavaScript Visualizer通过缩进每个新的执行上下文并使用独特的彩色背景来显示作用域链。 从视觉上可以看到,任何子执行上下文都可以引用位于其父执行上下文中任何变量,但反之则不行。



Earlier we learned that variables created inside of a function are locally scoped and they can't be (for the most part) accessed once the function's Execution Context has been popped off the Execution Stack. It's time to dive into that "for the most part". The one scenario where this isn't true is if you have a function nested inside of another function. In this case, the child function will still have access to the outer function's scope, even after the parent function's Execution Context has been removed from the Execution Stack. That was a lot of words. As always, JavaScript Visualizer can help us out here.

早先我们了解到,在函数内部创建的变量是局部作用域的,并且一旦将函数的执行上下文从执行堆栈中弹出,就不能( 大部分 )访问它们。 现在是时候“深入了解” 。 一个不正确的情况是,如果您有一个嵌套在另一个函数内部的函数。 在这种情况下,即使从执行堆栈中删除了父功能的执行上下文,子功能仍然可以访问外部功能的作用域。 那是很多话。 与往常一样,JavaScript Visualizer可以在这里为我们提供帮助。

Visualize the code yourself

自己可视化代码

Notice that after the makeAdder Execution Context has been popped off the Execution Stack, JavaScript Visualizer creates what's called a Closure Scope. Inside of that Closure Scope is the same variable environment that existed in the makeAdder Execution Context. The reason this happened is because that we have a function nested inside of another function. In our example, the inner function is nested inside of the makeAdder function, so inner creates a Closure over the makeAdder variable environment. Even after the makeAdder Execution Environment has been popped off the Execution Stack, because that Closure Scope was created, inner has access to the x variable (via the Scope Chain).

请注意,从执行堆栈弹出makeAdder执行上下文后,JavaScript Visualizer会创建所谓的Closure Scope 。 在Closure Scope内部,有一个与makeAdder执行上下文中存在的变量环境相同的环境。 发生这种情况的原因是因为我们有一个嵌套在另一个函数内部的函数。 在我们的示例中, inner函数嵌套在makeAdder函数inner ,因此inner函数会在makeAdder变量环境上创建一个Closure 。 即使在makeAdder执行环境从执行堆栈中弹出之后,由于创建了Closure Scopeinner也可以访问x变量(通过作用域链)。

As you probably guessed, this concept of a child function "closing" over the variable environment of its parent function is called Closures.

您可能已经猜到了,子功能在其父功能的可变环境上“关闭”的概念称为Closures



奖金部分 (Bonus Section)

Here are a few more related topics that I know if I don't mention someone will call me out on it ?.

这里还有一些其他相关主题,我是否知道我是否会提及有人会称呼我?

全局变量 (Global Variables)

In the browser, anytime you create a variable in the Global Execution Context (outside of any function), that variable will be added as a property on the window object.

在浏览器中,只要您在全局执行上下文中(在任何函数之外)创建变量,该变量都将作为属性添加到window对象上。

In both the browser and in Node, if you create a variable without a declaration (ie without var, let, or const), that variable will also be added as a property on the global object.

在浏览器和Node中,如果您创建没有声明的变量(即没有varletconst ),那么该变量也将作为属性添加到全局对象上。

// In the browser
var name = 'Tyler'function foo () {bar = 'Created in foo without declaration'
}foo()console.log(window.name) // Tyler
console.log(window.bar) // Created in foo without declaration

let和const (let and const)

这个关键字 (the this keyword)

In this article we learned that in the Creation phase of every Execution Context the JavaScript engine creates an object called this. If you want to learn more about why that's important and how to determine what the this keyword is, I'd suggest reading WTF is this - Understanding the this keyword, call, apply, and bind in JavaScript

在本文中,我们了解到在每个执行上下文的Creation阶段,JavaScript引擎都会创建一个名为this的对象。 如果您想更多地了解为什么这很重要以及如何确定this关键字是什么,我建议您阅读WTF-了解此关键字,在JavaScript中调用,应用和绑定

这是我们高级JavaScript课程的一部分
如果您喜欢这篇文章,请查看。 (This is part of our Advanced JavaScript course.
If you enjoyed this post, check it out.)

翻译自: https://www.freecodecamp.org/news/the-ultimate-guide-to-execution-contexts-hoisting-scopes-and-closures-in-javascript-876478dd4fbc/

js执行环境作用域和闭包

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://hbdhgg.com/2/7527.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 匯編語言學習筆記 Inc. 保留所有权利。

底部版权信息