# Scope Chain and this
JavaScript scope refers to the range of variables that can be accessed. There are three types Global scope, Function scope and Block scope.
A piece of JavaScript code needs to be compiled by the JavaScript engine before it is executed. After the compilation is completed, it will enter the execution phase.
On the basis of these, let's talk about what a scope chain is today.
# scope chain
First let's look at the following code:
function bar() {
console.log(scope)
}
function foo() {
var scope = "function"
bar()
}
var scope = "global"
foo()
What do you think the bar function and foo function print out in this code? This will analyze the execution flow of these two pieces of code.
Regarding scope chaining, many people will find it confusing, but if you understand the concepts of call stack, execution context, lexical environment, variable environment, etc., then you will understand the scope chain very easily.
In fact, in the variable environment of each execution context, an external reference is included to point to the external execution context. We call this external reference outer .
For example, when the above code is looking for the scope
variable, if it is not found in the current variable environment, the JavaScript engine will continue to search in the execution context pointed to by outer. We call this chain of lookups the scope chain.
Now you know that variables are searched through the scope chain, but there is still a question that has not been solved, the bar function called by the foo function, then why the external reference of the bar function is the global execution context, not the execution context of the foo function?
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 scoping.
# lexical scope
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 scope, which can predict how the code looks for the identifier during the execution process.
This may not be easy to understand, let's look at the following picture:
Because the JavaScript scope chain is determined by the lexical scope, the entire order of the lexical scope chain is: foo function scope -> bar function scope -> main function scope -> global scope.
That is to say, the lexical scope is determined at the compilation stage of the code, and has nothing to do with how the function is called.
All in all, the scope chain of the JavaScript language is determined by lexical scoping, which is determined by code structure( or position).
# this
We need to distinguish the scope chain and this are two different systems, and there is not much connection between them.
As can be seen from the figure, this
is bound to the execution context , that is to say, there is a this
in each execution context.
The execution context is mainly divided into three types - the global execution context, the function execution context and the eval execution context, so the corresponding this
is only these three - this
in the global execution context, this
in the function execution context and .this
in eval execution context
# this
in the global execution context
You can type console.log(this)
in the console to print out this in the global execution context, and the final output is the window object
. So we can come to the conclusion that this
in the global execution context refers to the window object. This is also the only point of intersection between this
and the scope chain
.
# this
in the function execution context
Let's look at the following code:
function foo(){
console.log(this)
}
foo()
We print out the this
value inside the foo function, execute this code, and the window object is also printed out, which means that when a function is called by default, the this in its execution context also points to the window object.
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 the this value in the function execution context.
1. Set by call method of the function
const protagonist = {
firstName: 'Sherlock',
lastName: 'Holmes',
getFullName: function() {
return this.firstName + ' ' + this.lastName;
}
};
const story = function(interest, hobby) {
console.log(this.getFullName() + ' loves ' + interest + ' and ' + hobby);
};
story.call(protagonist, 'reasoning', 'algorithm');
// story.apply(protagonist,['reasoning', 'algorithm']);
// const tellStory = story.bind(protagonist)
// tellStory('reasoning', 'algorithm')
The call()
method calls a function with a given this
value and arguments provided individually.
2. Set the method by calling the object
To change the this pointer in the function execution context, in addition to the call method of the function, we can also call it through the object, such as the following code:
const protagonist = {
firstName: 'Sherlock',
lastName: 'Holmes',
getFullName: function() {
console.log(this)
}
};
protagonist.getFullName()
So, we can come to the conclusion: use the object to call a method inside it, and the this of the method points to the object itself.
Let's do a little change:
const protagonist = {
firstName: 'Sherlock',
lastName: 'Holmes',
getFullName: function() {
console.log(this)
}
};
const fn = protagonist.getFullName
fn()
Execute this code, we will find that this
again points to the global window object.
So by comparing the above two examples, we can draw the following two conclusions:
- When calling a function in the global environment, the
this
inside the function points to the global variable window. - Calling a method inside of an object whose this in the execution context of the method points to the object itself.
- Set by the constructor
We can set this
in the constructor like this, as in the sample code below:
function CreateObj(){
this.name = "E"
}
const instance = new CreateObj()
In fact, when executing new CreateObj()
, the JavaScript engine does the following four things:
- First create an empty object
instance
; - Then call the
CreateObj.call
method and useinstance
as the parameter of the call method, so that when the execution context ofCreateObj
is created, its this points to theinstance
object; - Then execute the
CreateObj
function. At this time, thethis
in the execution context of theCreateObj
function points to theinstance
object; - Finally, the
instance
object is returned.
For better understanding, we can use code to demonstrate:
const instance = {}
CreateObj.call(instance)
return instance
In this way, we have constructed a new object through the new
keyword, and this
in the constructor is actually the new object itself.
# Summarize
The scope chain and this are two different systems, and there is not much connection between them. The only intersection between this and the scope chain is that, in some cases, they both point to the window object.
The scope chain is determined by lexical scoping, which is determined by code structure( or position). But this
is bound to the execution context.
If you feel that this
of some cases is not easy to identify, We can use const self = this
to convert this
system into a scope system.