This bird-article will help you thoroughly understand execution context, call stack, scope, variable promotion, this value, closure, and more

1. Execution context

1. Why have an execution context

When you usually write a project, you must write it file by file; Within each file, different methods and modules are broken down — I don’t think any student would cram thousands of lines of code logic into a single file. And when you do that, you’re actually practicing a very important idea in the software world — divide and conquer. Difficulties can only be intimidated by a coward idlers, but victory always belongs to those who dare to climb the heights of science. — MAO Yisheng Divide-and-conquer is a software writing strategy in which you reduce the complexity of a large problem by breaking it into small, specific problems and solving them one by one. In the code, it is to break up the large logic into independent code blocks. These “code blocks” have different names depending on granularity, and can be functions, modules, packages, and so on. To divide code logic into “chunks” is the wisdom of our programmers at the writing stage. It is the wisdom of the JS engine at the execution stage to divide the huge execution task into different execution contexts. We can think of the execution context as the engine once again “partitioning” the code during execution, again for the purpose of breaking down complexity.

2. What is the execution context

The JavaScript standard defines all the information required for the execution of a piece of code (including functions) as the “execution context” (which can be understood as the execution environment of the current code. The same function executed in different environments will produce different results due to the different data accessed), which is the execution infrastructure.

Execution context is defined differently in different versions, which is summarized in Relearning the Front End. There are three main versions:

For most of you, it’s probably the ES3 version, with scope chains and active variable objects and all that stuff, and that’s fine, but for those of you who read this, calm down, it’s not an error, it’s just a version problem.

If there are friends who still want to understand this version of the explanation in detail, I recommend a super good blog, will certainly help you.

Cavszhouyou. Top/JavaScript %…

1. The execution context in ES3 consists of three parts. A scope chain is also called a scope chain. Variable object: a variable object used to store variables. This value: This value.

2. In ES5, we improved the naming by changing the first three parts of the execution context to look like this. Lexical environment: lexical environment, used when fetching variables. (Variables created by let, const, with (), and try-catch exist in a lexical environment.) variable environment: used when declaring variables. (Variables declared by var or function () {} exist in the variable environment) This value: this value.

3. In ES2018, the execution context is again like this, subsumed by the lexical Environment, but with a few additions. Lexical environment: lexical environment, used when fetching variables or this values. Variable environment: used to restore the code execution position when declaring variables. Function: used when the task being executed is a Function. ScriptOrModule: used when executing a ScriptOrModule, indicating the code being executed. Realm: Base libraries and built-in object instances to use. Generator: Only the Generator context has this property, indicating the current Generator.

Here, I use the ES5 version to introduce to you, of course, the scope will also explain to you, do not get too tangled up in the execution context exactly what, the concept of locking (to tell the truth, I learned the word locking, make me very uncomfortable, at that time), please continue to read.

Note:

The variable environment of each execution context contains an external reference to the outer execution context. We call this external reference outer. This is related to the scope chain, as we will explain later.

3. Classification of execution context

There are three main execution contexts: global execution context, function execution context, and eval function execution context.

Global execution context: When JavaScript executes global code, the global code is compiled and the global execution context is created, and there is only one global execution context for the lifetime of the page.

2. Function execution context: Only when a function is called, the code inside the function is compiled and the function execution context is created. Generally, the function execution context is destroyed after the function is finished. Note that multiple calls to the same function create a new context. Details are covered in the call stack section below.

Eval execution context: When eval is used, the eval code is also compiled and the execution context is created. (Can be ignored)

4. Execute the context lifecycle

1. Global context

Execution context creation is divided into two phases: the creation phase and the execution phase.

1. Creation phase

The creation phase of the JS execution context is responsible for three things: determining this- creating LexicalEnvironment components – creating VariableEnvironment components. If you have read other articles about execution context read here must have a doubt, the execution context creation process is not should explain this, scope and the variable object/active object to it, how to talk to other place, here I am finally remind once again, this is ES3 version like this definition, this article? Mainly according to the ES5 version of the explanation, I will not continue to explain this problem, if you want to continue to understand the content of this version, here is a recommended article for you (a big guy concerned for a long time) :

Mp.weixin.qq.com/s?__biz=MzI…

Supplementary explanation:

A lexical environment is a structure that contains a mapping of identifiers that represent the names of variables/functions. Variables are references to actual objects (including function-type objects) or original values. Variable environment can be said to be lexical environment, it has all the attributes of lexical environment, as well as environmental record and external environment introduction. The only difference in ES6 is that the lexical environment is used to store variables declared by functions and let const, whereas the variable environment only stores variables declared by var.

2. Code execution stage

1. Creation phase

Create a global object (Window already exists) Create this and make it point to the global object allocate memory for variables and functions assign variables to undefined by default; Putting function declarations into memory creates scope chains

As you can see from the figure, the variable A, functions add, and addAll are stored in the variable environment object of the global context.

Or case 2:

Note:

The var declaration is set to undefined, the function is set to function, and the let const is set to uninitialized.

Now you know how variable promotion and function declaration advance work, and why let const has a temporary dead field. It’s because the JS engine initialized them differently during scope-creation.

2, let, const

‘let myname=’ geek time ‘

{

Console. log(myname) let myname= ‘geekbar

‘} `

VM171:5 Uncaught SyntaxError: Unexpected identifier

In block scope, the variable declared by the LET is promoted, but the creation of the variable is promoted, not the initialization. Using the variable before initialization creates a temporary dead zone. 【 extend 】

Var creation and initialization are improved, assignment is not. Let creation is improved, initialization and assignment are not. Function creation, initialization, and assignment are enhanced.

2. Executable code execution stage

Execute code in the global context:

For example, var is undefined in the creation phase and is assigned if it has a value. For example, let const is uninitialized and assigned if it has a value and undefined if it has no value.

Var name = 'xiuyan' var tel = '123456' function getMe() {return {name: name, tel: tel}}Copy the code

or

Global context execution flow:

Var name = 'xiuyan' var tel = '123456' function getMe() {return {name: name, tel: tel}}Copy the code

2. Variable promotion (Supplementary note)

In the execution context creation phase, the function declaration and var declaration variables have been assigned a value during the creation phase, the var declaration is set to undefined, the function is set to its own function, and let const is set to uninitialized.

Now you know how variable promotion and function declaration advance work, and why let const has a temporary dead field. It’s because the JS engine initialized them differently during scope-creation.

ShowName () console.log(myname) ---- undefined var myname = 'geeky time' function showName() {console.log(' function showName Be performed '); }Copy the code
{console.log(myname) let myname= 'vm232:4' Uncaught ReferenceError: Cannot access 'myname' before initializationCopy the code

Var creation and initialization are improved, assignment is not. Let creation is improved, initialization and assignment are not. Function creation, initialization, and assignment are enhanced.

Note:

Here are two rules for handling variables and functions with the same name: 1: If it is a function with the same name, the JavaScript compilation phase selects the one declared last. 2: If the variable has the same name as the function, the declaration of the variable is ignored at compile time

3. Function context

In a global context, the function context is created when a function call is executed

Case 1:

Var name = 'xiuyan' var tel = '123456' function getMe() {return {name: name, tel: tel}}Copy the code

When the engine reaches the getMe () call line, it first enters the creation phase of the function context, where the function context looks like this:

Case 2:

var a = 2 function add(b,c){ return b+c } function addAll(b,c){ var d = 10 result = add(b,c) return a+result+d } AddAll (3, 6)Copy the code

When the engine reaches the addAll(3,6) call, it first enters the creation phase of the function context. In this phase, the contents of the function context are as follows:

When the engine reaches the add(b,c) call, it first enters the creation phase of the function context. In this phase, the contents of the function context are as follows:

2. Call stack (execution context stack)

When we learn about local and functional scopes, we should know that context is sequential.

In a JavaScript program, if multiple execution contexts are created, the JavaScript engine handles them in a stack, which we call the Call stack. The bottom of the stack is always the global context, and the top is the context currently executing.

We see that after the function completes its execution, its execution context disappears. This vanishing process is called “out of the stack” – yes, during the execution of the JS code, the engine creates an “execution context stack” (also called the call stack) for us. Because there can be many function contexts, we cannot keep all of them. When a function completes execution, its corresponding context must relinquish the resources previously occupied. Context creation and destruction, therefore, correspond to an “on” and “off” operation. When we call a function, we push its context onto the call stack, exit the stack after execution, and then push the new function onto the stack.

Function context execution flow:

Case study:

var color = 'blue';

function changeColor() {
    var anotherColor = 'red';

    function swapColors() {
        var tempColor = anotherColor;
        anotherColor = color;
        color = tempColor;
    }

    swapColors();
}

changeColor();
Copy the code

Reference article:

Segmentfault.com/a/119000001…

Conclusion:

Each time a function is called, the JavaScript engine creates an execution context for it, pushes the execution context onto the call stack, and then the JavaScript engine starts executing the function code.

If another function B is called from function A, the JavaScript engine creates an execution context for function B and pushes the execution context of function B to the top of the stack.

After the current function completes execution, the JavaScript engine pops the execution context of that function out of the stack.

A stack overflow problem occurs when the allocated call stack space is used up.

The bottom of the stack is always the global context, and the top is the context currently executing.

Note:

1, call stack has two indicators, the maximum stack capacity and maximum call depth, meet any one of the stack overflow, but the specific size and depth, this has not been studied, there are friends who know, introduce to leave a message for communication.

3. Scope, scope chain

1. Scope

Scope is the area in the program where variables are defined, and the location determines the lifetime of the variables. Commonly understood, scope is the accessible scope of variables and functions, that is, scope controls the visibility and life cycle of variables and functions.

Prior to ES6, ES had only two scopes: global scope and function scope.

Objects in a global scope can be accessed anywhere in the code, and their life cycle follows that of the page. A function scope is a variable or function defined inside a function and can only be accessed inside the function. After the function is executed, the variables defined inside the function are destroyed.

For those of you who have read the previous part of the article, it is completely understandable why the variable declared by var can be promoted.

I’m going to focus on const,let declaration variables.

Variables declared with the let keyword can be changed. Variables declared with const cannot be changed.

Either way, both can generate block-level scopes.

All variables declared by var inside the function are stored in the variable environment at compile time. Variables declared by let are deposited in the Lexical Environment at compile time. Inside the scope of the function, variables declared by let are not stored in the lexical environment.

Case study:

function foo(){
var a = 1
let b = 2
{
let b = 3
var c = 4
let d = 5
console.log(a)
console.log(b)
}
console.log(b)
console.log(c)
console.log(d)
}
foo()
Copy the code

Block-level scope is implemented through the stack structure of the lexical environment, and variable promotion is implemented through the variable environment. By combining the two, the JavaScript engine supports both variable promotion and block-level scope. (See the reference article for details on how to execute the function. It’s too late for me to do it. I’m going to fall asleep.)

Scope reference article:

1, time.geekbang.org/column/arti…

Note:

LexicalEnvironment creates object environment loggers that define the relationship between variables that appear in the global context and function contexts. This logger is used to register variable declarations such as let const class.

2. Scope chain

In the variable environment of each execution context, there is an external reference to the external execution context, which we call outer.

When a piece of code uses a variable, the JavaScript engine first looks for the variable in the current execution context. For example, if the myName variable is not found in the current context, The JavaScript engine then continues to look in the execution context that Outer points to.

Case study:

Function bar() {console.log(myName)} function foo() {var myName = "geeky" bar()} var myName = "geeky" foo()Copy the code

As you can see from the figure, the outer of bar and foo both point to the global context, which means that if an external variable is used in bar or foo, the JavaScript engine will look in the global execution context. We call this chain of lookup the scope chain.

Is there anything confusing about this picture? Is it a little different from the usual perception? You also don’t say, I just learned when, I suspect is not the author write wrong knowledge point, all want to start to spray, fortunately endure, otherwise the clown turned out to be my own, harm, vegetable chicken, is really difficult,…

To answer this question, you also need to know what lexical scope is. This is because during JavaScript execution, its scope chain is determined by lexical scope. Honestly, what is lexical scope? I’ve never heard of it before. Keep it up, guys. Manager Jonben, keep it up.

Lexical scope means that the scope is determined by the position of the function declaration in the code, so the lexical scope is a static operational field that predicts how the code will look up identifiers during execution.

JavaScript scope chains are determined by lexical scope, which is where your code is located,

The lexical scope is determined at compile time, regardless of how the function is called.

Now that we know about lexical scope and scope chains in JavaScript, let’s go back to the question above: Function foo called function bar in the first section of code, so why is the external reference to function bar the global execution context, and not foo’s? This is because the parent scopes of foo and bar are both global scopes according to lexical scope, so if foo or bar functions use a variable they don’t define, they will look it up in the global scope. That is, lexical scope is determined at the code stage, regardless of how the function is called.

4, closures

Once you understand the concepts of variable environments, lexical environments, and scope chains, you’ll have a much easier time understanding what closures are in JavaScript.

What are closures?

In JavaScript, according to the rules of lexical scope, an inner function can always access the variables declared in its outer function. When an inner function is returned by calling an outer function, the variables referenced by the inner function remain in memory even after the outer function has finished executing. Let’s call this set of variables a closure. For example, if the external function is foo, then the set of variables is called the closure of the foo function.

Just look at the text, may not be familiar with the closure of the students, easy to deceive, or first find a demo to see.

Closure cases

Function foo() {var myName = "geeker time" let test1 = 1 const test2 = 2 var innerBar = {getName:function(){ console.log(test1) return myName }, Function (newName){myName = newName}} return innerBar} var bar = foo() bar.setName(" geeky ") bar.getName() console.log(bar.getName())Copy the code

As you can see from the code above, the innerBar is an object with two methods: getName and setName. As you can see, both methods are defined inside foo, and both methods use myName and test1 internally.

According to the rules of lexical scope, the inner functions getName and setName always have access to variables in their outer function foo, so when the innerBar object is returned to the global variable bar, even though foo has performed the bunting, But the getName and setName functions can still use the myName and test1 variables in foo. So when foo completes, its entire stack looks like this:

Ok, now we can finally give closure a formal definition. In JavaScript, according to the rules of lexical scope, an inner function can always access the variables declared in its outer function. When an inner function is returned by calling an outer function, the variables referenced by the inner function remain in memory even after the outer function has finished executing. Let’s call this set of variables a closure. For example, if the outer function is foo, the set of variables is called the closure of the foo function.

As you can see from the figure, there is no myName variable in the execution context of setName. Foo’s closure contains the variable myName, so calling setName changes the value of myName variable in foo’s closure. Similarly, when bar.getName is called, the variable myName is called in the foo function closure.

3. Google Chrome F12 to view closures

To see what closures look like, go to Developer Tools, open Chrome’s Developer Tools, hit a breakpoint anywhere in the bar function, and refresh the page to see something like this:

Closure display in developer tools

When bar.getName is called, the Scope item on the right represents the Scope chain: Local – >Closure(foo) – >Global – > Local – >Closure(foo) – >Global – > Local – >Closure(foo) – >Global In the future, you can also use Scope to see the Scope chain of the actual code, which will be easier to debug the code.

4. Closure recycling

Now that we understand what a closure is, let’s talk a little bit about when a closure is destroyed. Because closures can easily leak memory if used incorrectly, paying attention to how closures are recycled can help you use closures properly. In general, if the function referencing a closure is a global variable, the closure will remain in place until the page closes; But if this closure is not used in the future, it can cause a memory leak. If the function referencing the closure is a local variable, the JavaScript engine’s garbage collector will reclaim the memory if the closure is no longer in use the next time the JavaScript engine performs garbage collection.

When using closures, try to follow the same principle: if the closure is used forever, it can exist as a global variable. But if it is used infrequently and takes up a lot of memory, try to make it a local variable.

This article is just a brief introduction to the problem of closure collection. In fact, it also involves JavaScript garbage collection mechanism. As for garbage collection, it will be explained in detail in a future article.

You can do your own research.

5, this

What is this, why do we need this keyword, understand the context, can help us better understand this, and use it.

1. Why is this keyword needed

Let’s start with an example

var bar = { myName:"time.geekbang.com", printName: Function () {console.log(myName)}} function foo() {let myName = "geeky time" return bar.printname} let myName = "geeky time"  let _printName = foo() _printName() bar.printName()Copy the code

As you probably already know, the variable myName used in printName is under the global scope, so the final printed value is “Geekbang”. This is because the scope chain of the JavaScript language is determined by lexical scope, which is determined by code structure.

However, as a general rule, when calling the bar.printName method, the variable myName inside the method should be used in the BAR object, because they are all in one piece, as is the way most object-oriented languages are designed.

So it is a very common requirement to use properties inside objects in methods inside objects. However, JavaScript’s scoping mechanism does not support this, so JavaScript has developed another this mechanism based on this requirement.

In the writing of some functions or methods, this allows us to reference objects in a more convenient way. In the design of some API, the code is more concise and easy to reuse.

I believe that now you should be more clear about why we need this, of course, this is only one of the reasons, the other reasons, we need to complement and improve.

2. What is this

As you can see from the figure, this is bound to the execution context, meaning that each execution context has a this.

But before I go into this, I want you to make a clear distinction that scoped chains and This are two different systems, and there’s not much between them. Knowing this early on will help you avoid unnecessary associations with scopes as you learn this.

2

There are three main execution contexts — global execution context, function execution context, and eval execution context, so there are only three corresponding this — this in the global execution context, this in the function context, and this in eval (ignored).

First let’s look at what this is in the global execution context. You can print out this in the global execution context by typing console.log(this) in the console, and the final output is the Window object. So you can conclude that this in the global execution context refers to the window object. This is also the only point where this intersects the scope chain, the bottom of which contains the Window object, and this in the global execution context also refers to the window object.

2. This in the context of function execution

Now that you know that this in the global object refers to the window object, let’s focus on parsing this in the context of function execution. Let’s start with the following code:

function foo(){
console.log(this)
}
foo()
Copy the code

We print this inside foo, execute this code, and print out the window object as well, indicating that by default we call a function whose execution context this also refers to the window object.

3. This points to the object

var bar = { myName:"time.geekbang.com", printName: Function () {console.log(this.myname) ----time.geekbang.com}} function foo() {let myName = "geek time" return Bar.printname} let myName = "geekbang" let _printName = foo() _printName() bar.printName()Copy the code

If we add this to the bar object, the printed myName is the value of the bar object, whereas if we don’t add this, we are referring to the global context. I feel the need to add this as a taxonomy. You can test it by removing this yourself.

After all that this points to, you might wonder, can we set this in the execution context to point to other objects? The answer is yes. In general, there are three ways to set this in the context of function execution.

4. Change the this pointer

You can use the call method of a function to set this to the execution context. For example, in this code, instead of calling foo directly, we call foo’s call method. And take the bar object as an argument to the call method.

Let bar = {myName: "geeky ", test1: 1} function foo(){this.myname = "geeky time"} foo.call(bar) console.log(bar) console.log(myName)Copy the code

By executing this code and looking at the output, you can see that this inside foo already points to the bar object, because by printing the bar object, you can see that bar’s myName property has changed from “geek state” to “geek time.” Also print myName in the global execution context, and the JavaScript engine says the variable is undefined. In addition to the call method, you can also use bind and apply to set this in the execution context of a function. There are some differences between these two methods, so you can search for them and learn how to use them if you are interested.

2. Call method Settings through the object

To change the this reference in the execution context of a function, you can use a call to the object in addition to the call method, as in the following code:

Var myObj = function(){console.log(this)}} myobj.showthis ()Copy the code

In this code, we define a myObj object consisting of a name attribute and a showThis method, and then call the showThis method from the myObj object. By executing this code, you can see that the final output value of this refers to myObj. So, you can conclude that you use an object to call a method inside it whose this refers to the object itself.

Note:

Call a function in the global environment. This inside the function refers to the global variable Window. Call a method within an object whose execution context this refers to the object itself.

3. Set it in the constructor

Function CreateObj(){this.name = "CreateObj"} var myObj = new CreateObj()Copy the code

We build a new object with the new keyword, and this in the constructor is the new object itself.

Refer to this article for details of new:

Developer.mozilla.org/zh-CN/docs/…

This is a good idea.

1, mp.weixin.qq.com/s/hYm0JgBI2…

2, time.geekbang.org/column/arti…

In the process of sorting out articles and collecting data, I found that every knowledge point was too big, and there were many interesting knowledge points worth learning if I went further. In the later stage, I would work out a more detailed problem of JavaScript for these individual knowledge points

What do you want to learn or want to discuss the difficult problems of JavaScript, you can leave a message to the big shopkeeper, with more communication, standing on the shoulders of predecessors giants, we work hard together to learn.

Reference article:

1, the front-end interview thoroughly understand this point to mp.weixin.qq.com/s?__biz=MzI…

2, JavaScript, in-depth understanding of the scope chain cavszhouyou. Top/JavaScript %…

3, detailed illustration segmentfault.com/a/119000001 execution context…

4, read an article JS execution context www.cnblogs.com/echolun/p/1…

Block-level scope: Var flaws and why let and const were introduced? Time.geekbang.org/column/arti…

6, new operator developer.mozilla.org/zh-CN/docs/…

7, this: from the Angle of JavaScript execution context clear this time.geekbang.org/column/arti…