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.