- scope chain
These concepts are more comfortable to understand than they look like, especially with the knowledge of the execution context.
A variable lookup could be confusing in the following example.
When executing the
isApple function, we have three stacked execution contexts in position:
- the global execution context
isBananafunction execution context
isApplefunction execution context
Next, the console begins looking up the
Intuitively, we may analyze the chain lookup by following a top-to-bottom flow in the call stack. The console would log “banana” because it finds the
apple variable in the
isBanana function execution context.
By contrast, the console actually logs “apple” assigned in the global execution context.
Our chain lookup missed a critical component in the execution context, the outer.
If we look into the
isApple execution context, its outer points to the global execution context.
apple variable in the global execution context immediately after failing to find it in the
isApple execution context.
Did the mystery resolve?
Not really. The outer concept leads to another question.
Why the outer of
isApple execution context points to the global one instead of the
After all, the
isApple function is called inside of the
isBanana. Shouldn’t the scope chain follow the call stack?
From the two-step process perspective, the scope chain is defined at the compiling step, not the execution step.
Let’s take a look at the same example from a lexical scope perspective.
In this case, the
isBanana functions are declared in the global scope. Therefore, their lexical scopes are the global scope.
To better understand this feature, let’s take a look at another example. Instead of declaring functions in the global scope, we indent each function inside of the previous one.
In this case,
priceAis defined in the global scope;
priceBis defined in the
priceCis defined in the
Based on lexical scopes, we can reason the outer in each execution context:
priceCexecution context, the outer points to
priceBexecution context, the outer points to
priceAexecution context, the outer points to the global execution context.
At the end of the execution, the console logs “30.”
The closure is more straightforward to understand than it sounds. Let’s take a look at an example.
We have the following call stack right before the
util is returned and assigned to the
After returning the
applePrice function execution ends, and its execution context is removed.
Meanwhile, variable and lexical environments disappear, and variables inside of them are supported to be destroyed.
Here, the inner functions are
setPrice, and the outer function is
getPrice function uses two variables,
setPrice function uses the
Following the rule,
price variables are kept in a separate area. It is an exclusive area where can only be accessed by
setPrice function, also known as the closure.
discount variable is destroyed because no methods hold a reference to it.
Next, the execution continues and calls the
price variable in the closure. The value of the
price is set to “20.”
In the last line, the
price variables in the closure and logs out “apple” and “20” correspondingly.
The execution ends.
By running the example codes in your Chrome, you can see the closure in its dev tools.
“This” is not part of a scope chain
We have touched three components in an execution context:
- variable environment
- lexical environment
The last one is
Each scope has it’s
If we log
this in the global scope, we receive a
window is the only element where
this and scope concept joins because it is part of the global scope located at the root end of the scope chain.
How about the
this in a function scope?
this referred to the
Interestingly, the console logs the
window object, the same as it runs in the global scope.
this is not related to any scope concepts.
But who is
this? Is it always the
Who is “this”?
Let’s take a look at an example.
In this example, the
getPrice logs “10”, and
getThis logs the
So, we found the answer: whoever calls the method is
While the outer defined at the compiling step,
this is determined at the execution step.
When a function declared, it is attached to the
window object. When you execute a function, it is the
window object which calls the function. Consequently,
this is the
We can reset the
this by changing the caller.
In the last line, we use the
call function to change the
this to the
banana object calls the
getPrice function. Hence,
banana, and the console logs “20”.
Convert this to the scope concept
this has nothing to do with the scope, we can easily convert it to the scope concept.
The following example shows a typical gotcha moment in using
Who calls the
At a glance, it looks like the
getPrice calls it. However, the console logs the
So far, we know a function (or method) is either called by an object or the
window, not by a function. In this case, it is the
window calls the
this doesn’t inherit from the outer scope because it has never been a part of the scope concept.
We can quickly fix this issue by assigning
this to a local variable.
By doing so, the scope chain starts working.
Since ES6, we have the arrow function to avoid using a redundant
An arrow function doesn’t convert the
this to a scope concept. Instead, it simply doesn’t create an execution context and shares the same
this of the method.
What are the takeaways?
- The outer defined the variable chain lookup, AKA, the scope chain.
- The lexical scope defines the outer, and where you write functions sets the lexical scope.
- The scope chain is determined at the compiling step, not the execution step. Hence, a function call, which happens at the execution step, doesn’t affect the scope chain.
- The closure appears because of the lexical scope rule — an inner function can always access to variables in its outer function. It is exclusive to the functions holding the variable references.
thisis not a scope concept. Whoever calls the function (or method) is
- This is a post about the execution context. You can find more information about the outer.
In the previous post, we talked about the execution context. It is the first execution context created at the compiling…