The scope of JavaScript
is static
, but this
in Js
is an exception. The issue of what this
refers to is more akin to dynamic
scope, as it doesn't concern itself with how functions and scopes are declared or where, but rather with where they are called from. The reference of this
cannot be determined when a function is defined, only when it is executed. In practice, this
ultimately points to the object that calls it.
First, let's understand the scope of JavaScript
to grasp why this
is more similar to dynamic scope. In a program, the names used may not always be valid or available, and the code scope that limits the availability of a name is its scope
. When a method or member is declared, it has the current executable context. In a specific context where expressions are visible and can be referenced, if a variable or expression is not within the current scope, it cannot be used. Scopes can also be layered hierarchically based on the code hierarchy, allowing child scopes to access parent scopes.
JavaScript
has a static
scope, also known as lexical
scope, where when encountering a variable not defined as a parameter or in the function's local scope, it looks up in the context of the function's definition. In contrast, dynamic
scope functions look up in the context of the function call.
Calling s()
prints a
as 1
, which is characteristic of static
scope. If it was dynamic
scope, it would have printed 2
. Most languages now use static
scope, such as C
, C++
, Java
, PHP
, Python
, etc., while languages with dynamic
scope include Emacs Lisp
, Common Lisp
, and Perl
.
Variables or methods declared directly at the top level run in the global scope. By using the [[Scopes]]
attribute of a function, we can access the scope chain, which stores the function's scope chain and is an internal property of the function that cannot be directly accessed, but can be viewed by printing.
After declaring a function, the running environment for methods or members declared within the function is the function's function scope.
If let
or const
exists within a code block, a closed scope is formed from the beginning of the block for these declared variables.
Before using this
, it's necessary to understand why JavaScript
has this design. Before that, let's consider a small example. Typically, when using this
, we may encounter a typical problem like the following, where even though we are running the same function, the result may differ.
The reason for such a result is because the this
keyword is used. As mentioned earlier, the value of this
must be determined at runtime. In the case of obj.say()
, the environment in which say()
is executed is the obj
object, while for window.say()
, the say()
runs in the window
object. So, the results of the two function calls are different.
Now, let's understand why JavaScript has such a design. First, let's understand the stack in the memory structure of JavaScript. The heap is a dynamically allocated memory with an indefinite size and no automatic release. The stack is an automatically allocated memory space that is automatically released during code execution. JavaScript provides an environment for executing Js code in the stack memory. Scope and function calls are all executed in the stack memory. In JavaScript, basic data types such as String
, Number
, Boolean
, Null
, Undefined
, Symbol
occupy small space with a fixed size, and their values are directly stored in the stack memory for value access. For reference data type Object
, its pointer is placed in the stack memory, pointing to the actual address in the heap memory, and is accessed by reference.
Now let's look at the example above. In the memory, the obj
object is stored in the heap memory. If the property value in the object is a basic data type, it will be stored in the same memory area as the object. However, this property value might also be a reference data type. For the say
function, it also exists in the heap memory. In fact, in this case, we can understand that this function is actually defined in a memory area (exists in the form of an anonymous function), and the obj
object also exists in another memory area. obj
points to the memory address of this anonymous function through the say
property: obj --say--> function
. As a result of this memory structure, any variable object can be made to point to this function. Therefore, JavaScript functions need to allow us to access the value of the runtime environment, so we need a mechanism to obtain the current execution environment context
within the body of the function. This is why this
appears, and its design purpose is to represent the current execution environment of the function body.
We need to remember that this
is bound at runtime, not at the time of definition. Its context
depends on various conditions when the function is called. Simply put, the binding of this
has no relationship with the position of the function declaration; it only depends on the way the function is called. In simple terms, this
always refers to the caller, except for arrow functions. Next, let's introduce the five usage scenarios of this
.
The most commonly used function call type is an independent function call, which has the lowest priority. In this case, this
points to the global object. Note that if using strict mode, the global object will not be used for default binding, so this
becomes undefined
.
In an object property reference chain, only the top level or the last level affects this
. Again, this
always refers to the caller, more specifically, it points to the nearest caller. Of course, this rule does not apply to arrow functions. Additionally, we may intentionally or unintentionally create cases of indirect reference, where this
also refers to the caller. The examples used in the preceding analysis belong to cases of indirect reference.
If we want to forcibly bind a function to a specific environment or object, we can use apply
, call
, or bind
to bind this
to execute. Each Function
object has apply()
, call()
, and bind()
methods, which can be used to call a function in a specific scope, effectively setting the value of the this
object within the function. It is worth noting that using bind
to bind this
takes precedence over apply
and call
. After using bind
to bind this
, it is not possible to change the value of this
using apply
or call
.
In JavaScript, new
is a syntactic sugar that simplifies code writing and can be used to create object instances in batches. The following operations are actually performed in the process of new
.
{}
.this
.Arrow functions do not have their own this
. When using this
in the function body of an arrow function, it will obtain the this
in its context environment. When an arrow function is called, it does not generate its own scope for this
; it only inherits this
from the scope chain one level up. As arrow functions do not have their own this
pointer, using apply
, call
, or bind
can only pass arguments and cannot dynamically change the this
binding of the arrow function. Additionally, arrow functions cannot be used as constructors and will throw an exception when instantiated with new
.