this and ScopeChain in EcmaScript

Hello, Habr!



In a previous article, we examined the general theory of OOP as applied to EcmaScript and the popular fallacy of novice developers regarding the differences between OOP in JS and classical languages.



Today we’ll talk about two other equally important EcmaScript concepts, namely, the relationship of the entity with the execution context ( this is this connection) and the relationship of the entity with the generating context ( ScopeChain ).



So, let's begin!



this



At interviews in response to the question: "Tell us more about this .". Novice developers, as a rule, give very vague answers: " this is the object" before the point "that was used to call the method," this is the context in which the function was called, "etc. ...



In fact, the situation with this central concept for EcmaScript languages ​​is somewhat more complicated. Let's figure it out in order.



Let's say we have a JavaScript program that has variables declared globally; global functions; local functions (declared inside other functions), functions returned from functions.



const a = 10; const b = 20; const x = { a: 15, b: 25, } function foo(){ return this.a + this.b; } function bar () { const a = 30; return a + b; } function fooBaz(){ function test () { return this.a + this.b; } return test(); } function fooBar() { const a = 40; const b = 50; return function () { return a + b; } } fooBar()();
      
      





When transferring control to the executable code, an entry is made into the execution context. Executable code - this is any code that we execute at a given moment in time, it can be a global code or a code of any function.



The execution context is an abstraction that typifies and delimits code. From the point of view of this abstraction, the code is divided into global (any connected scripts, inline scripts) and function code (code of nested functions does not belong to the context of parent functions).



There is a third type - EvalCode. In this article, we neglect it.



Logically, the set of execution contexts is a stack operating on the principle of Last-in-First-out (lifo). The bottom of the stack is always the global context, and the top is the current executable. Each time a function is called, an entry is made into its context. When a function completes, its context ends. Spent contexts are removed from the stack sequentially and in reverse order.



Take a look at the code above. We have a call to the fooBar function in global code. In the fooBar function , we return an anonymous function that we immediately call. The following changes occur with the stack: the global context gets into it - when fooBar is called , its context gets on the stack - the fooBar context ends, returns an anonymous function and is removed from the stack - an anonymous function is called, its context gets on the stack - an anonymous function fulfills, returns a value and its context is deleted from the stack - at the end of the script, the global context is deleted from the stack.



The execution context can be conditionally represented as an object. One of the properties of this object will be the Lexical Environment (LO).



The lexical environment contains:





When entering the execution context, the interpreter scans the context. All variable declarations and function declarations go up to the beginning of the context. Variables are created equal to undefined, and functions are completely ready to use.



this is also a property of the execution context, but not the context itself, as some novice interviewers answer! this is defined when entering the context and remains unchanged until the end of the context's lifetime (until the context is removed from the stack).



In the global execution context, this is defined depending on strict mode : when strict mode is off, this contains a global object (in the browser it is proxied to the top level in the window object), with 'use strict' this is undefined.



this in the context of functions - the question is much more interesting!

this in functions is determined by the caller and depends on the syntax of the call. For example, as we know, there are methods that allow this to be fixed rigidly when called ( call , apply ) and a method that allows you to create a wrapper with “fixed this” ( bind ). In these situations, we explicitly specify this and there can be no doubt about its definition.



With a normal function call, the situation is much more complicated!



One of EcmaScript built-in types, ReferenceType , will help us understand how this is affixed in functions. This is one of the internal types available at the implementation level. Logically, it is an object with two base properties (a reference to a certain base object for which a ReferenceType is returned), propertyName (a string representation of the object identifier for which a ReferenceType is returned).



ReferenceType is returned for all variable declarations, function declarations, and property accesses (this is the case that interests us in terms of understanding this).



The rule for defining this for functions called in the usual way:

If the ReferenceType is located to the left of the function activation brackets, then the base of this ReferenceType is put in this



function.
If any other type is to the left of the brackets, then a global object or undefined



put in this



(in fact, null



put, but since null does not have a specific value from the point of view of ecmascript, then it is cast to a global object, a reference to which can be equals undefined



depending on strict mode).




Let's look at an example:



 const x = 0; const obj = { x: 10, foo: function() { return this.x; } } obj.foo();//  10 ..    ReferenceType  base     obj const test = obj.foo;//       test();//  0 ..  test()   .test(),..  base    ,       0.
      
      





I think the method of definition is illustrated clearly. Now consider a few less obvious cases.



Functional Expressions



Let's go back to our ReferenceType for a second. This type has a built-in GetValue method that returns the true type of the object obtained through the ReferenceType. In the expression zone, GetValue always fires.



Example:



 (function (){ return this;// this     undefined    strict mode })()
      
      





This is due to the fact that GetValue always works in the expression zone. GetValue returns a Function type, and to the left of the activation brackets is not a ReferenceType. Recall our rule for determining this : If any other type is to the left of the brackets, then a global object is put in this



or undefined



(actually null



, but since null does not have a specific value from the point of view of ecmascript, it is converted to a global object , the link to which can be equal to undefined depending on strict mode)
.



Expression zones are: assignment (=), operators || or other logical operators, ternary operator, array initializer, comma-separated list.



 const x = 0; const obj = { x: 10, foo: function() { return this.x; } } obj.foo(); //        //  ? (obj.foo)(); // ,    , GetValue   // ? (obj.foo = obj.foo)(); //        GetValue,     Fuction,   ReferenceType,   0   (   this) //  ||    ,    ..? (obj.foo || obj.foo)();// 0    ,     //  [obj.foo][0]();// 0    ,     // ..
      
      





Identical situation in named functional expressions. Even with a recursive call to this, a global object or undefined







this nested functions called in the parent



Also an important situation!



 const x = 0; function foo() { function bar(){ return this.x; } return bar(); } const obj = {x:10}; obj.test = foo; obj.test();// undefined
      
      





This is because the call to bar()



equivalent to the call to LE_foo.bar



, and the object of the lexical environment puts undefined as this.



Constructor functions



As I wrote above:
this in functions is determined by the caller and depends on the syntax of the call.


We invoke constructor functions using the new keyword. The peculiarity of this method of function activation is that the internal function method [[construct]] is called, which performs certain operations (the mechanism for creating entities by designers will be discussed in the second or third article on OOP!) And calls the internal [[call]] method, which puts down in this created instance of the constructor function.



Scope Chain



The scope chain is also a property of the execution context like this. It is a list of objects of lexical environments of the current context and all generating contexts. It is in this chain that the search for variables occurs when resolving identifier names.



Note: this associates a function with an execution context, and ScopeChain with child contexts.



The specification states that ScopeChain is an array:



  SC = [LO, LO1, LO2,..., LOglobal];
      
      





However, in some implementations, such as JS, the scope chain is implemented through linked lists .



In order to better understand ScopeChain, we will discuss the life cycle of functions. It is divided into the creation phase and the execution phase.



When a function is created, it is assigned the internal [[SCOPE]] property.

In [[SCOPE]] , a hierarchical chain of objects of lexical environments of higher (generating) contexts is recorded. This property remains unchanged until the function is destroyed by the garbage collector.



Note! [[SCOPE]] , unlike ScopeChain, is a property of the function itself, not its context.



When a function is called, its execution context is initialized and filled. The context is affixed with ScopeChain = LO (of the function itself) + [[SCOPE]] (hierarchical chain of LO affecting contexts).



Resolution of identifier names - sequential polling of LO objects in the ScopeChain chain from left to right. The output is a ReferenceType whose base property points to the LO object in which the identifier was found, and PropertyName will be a string representation of the identifier name.



This is how the closure is arranged under the hood! A closure is essentially the result of a search in ScopeChain for all variables whose identifiers are present in the function.



 const x = 10; function foo () { return x; } (function (){ const x = 20; foo();// 10 ..     <b><i>[[SCOPE]]</i></b> foo          })()
      
      





The following example illustrates the life cycle [[SCOPE]] .



 function foo () { const x = 10; const y = 20; return function () { return [x,y]; } } const x = 30; const bar = foo();//   ,   foo    bar();// [10,20] .. [[SCOPE]]    foo         
      
      





An important exception is the constructor function . For this type of function, [[SCOPE]] always points to a global object.



Also, do not forget that if one of the links in the ScopeChain chain has a prototype, then the search will be carried out in the prototype too.



Conclusion



We will put out the key ideas thesisally:





Hope the article was helpful. Until future articles, friends!



All Articles