Introduction
In this article, we will discuss more details directly related to execution context. The topic of discussion is this keyword. Practice has proven that this topic is difficult, and problems often occur in the determination of this in different execution contexts.
Many programmers are accustomed to believing that in programming languages, the this keyword is closely related to object-oriented program development, and it completely points to the object newly created by the constructor. This is also implemented in the ECMAScript specification, but as we will see, in ECMAScript, this is not limited to only pointing to newly created objects.
English translation: Dmitry A. Soshnikov with help from Stoyan Stefanov
Published: 2010-03-07
http://dmitrysoshnikov.com/ecmascript/chapter-3-this/
Original Russian text: Dmitry A. Soshnikov
Correction: Zerogif
Released: 2009-06-28;
Updated: 2010-03-07
http://dmitrysoshnikov.com/ ecmascript/ru-chapter-3-this/
Most of the content of this article refers to: http://www.denisdeng.com/?p=900
Some sentences refer to: justin’s Chinese translation
Copy code
Let us take a closer look at what exactly is this in ECMAScript?
Definition
this is an attribute in the execution context:
activeExecutionContext = {
VO: {...},
this: thisValue
};
The VO here is what we discussed in the previous chapter variable object.
This is directly related to the type of executable code in the context. The value of this is determined when entering the context and remains unchanged while the context is running.
Let's study these cases in more detail:
This in global code
Everything is simple here. In global code, this is always the global object itself, so it is possible to refer to it indirectly.
// Display the attributes that define the global object
this .a = 10; // global.a = 10
alert(a); // 10
// Implicit by assigning to an unidentified
b = 20;
alert(this.b); // 20
// is also implicitly declared through variable declaration
// because the variable object of the global context is the global object itself
var c = 30;
alert(this.c); // 30
this in function code
It is interesting when using this in function code, this situation is difficult and can cause a lot of problems.
The first (perhaps the most important) feature of this value in this type of code is that it is not statically bound to a function.
As we mentioned above, this is determined when entering the context. In a function code, this value is completely different every time.
In any case, the value of this is unchanged while the code is running, that is, since it is not a variable, it is not possible to assign a new value to it (in contrast, in the Python programming language, it Well-defined as the object itself, which can be continuously changed during runtime).
var foo = {x: 10};
var bar = {
x: 20,
test: function () {
alert(this === bar); // true
alert(this.x) ; // 20
this = foo; // Error, the value of this cannot be changed at any time
alert(this.x); // If there is no error, it should be 10, and Not 20
}
};
// When entering the context
// this is regarded as the bar object
// determined as "bar" object; why so - will
// be discussed below in detail
bar.test(); // true, 20
foo.test = bar.test;
// However, here this still will not be foo
// Although the same function is called
foo.test(); // false, 10
So, there are several factors that affect the change of this value in the function code:
First of all, in a normal function call, this is provided by the caller who activates the context code, that is, calling the function 's parent context. this depends on how the function is called.
In order to accurately determine the value of this in any situation, it is necessary to understand and remember this important point. It is the way the function is called that affects the this value in the context of the call, nothing else (we can see in some articles, even in books about javascript, they claim: "the this value depends on how the function is defined, If it's a global function, this is set to the global object, if the function is a method of an object, this will always point to the object - this is absolutely not true"). Continuing our topic, we can see that even normal global functions will be activated by different forms of calling methods. These different calling methods lead to different this values.
function foo() {
alert(this) ;
}
foo(); // global
alert(foo === foo.prototype.constructor); // true
// But the same For different calling expressions of a function, this is different
foo.prototype.constructor(); // foo.prototype
may be used as a method defined by some objects The function is called, but this will not be set to this object.
var foo = {
bar: function () {
alert(this);
alert(this === foo);
}
};
foo.bar(); // foo, true
var exampleFunc = foo.bar;
alert(exampleFunc === foo.bar); // true
// Once again, different calling expressions for the same function , this is different
exampleFunc(); // global, false
So, how does the way of calling the function affect the this value? In order to fully understand the determination of this value, one of its internal types - reference type (Reference type) needs to be analyzed in detail.
Reference type (Reference type)
Using pseudocode, we can represent the value of the reference type as an object with two properties - base (that is, the object with the properties), and base in propertyName.
var valueOfReferenceType = {
base:
,
propertyName:
};
There are only two situations for reference type values:
When we deal with an identifier or a property accessor
identifier will be discussed in detail in the next article. Here we only need to know that the return value of this algorithm is always a reference type value (this is different for this It’s important to say).
Identifiers are variable names, function names, function parameter names and unrecognized property names in global objects. For example, the value of the following identifier:
var foo = 10 ;
function bar() {}
In the intermediate result of the operation, the value corresponding to the reference type is as follows:
var fooReference = {
base: global,
propertyName: 'foo'
};
var barReference = {
base: global,
propertyName: 'bar'
};
In order to get the real value of an object from the reference type, in the pseudo code The GetValue method can be described as follows:
function GetValue(value) {
if (Type(value) != Reference) {
return value;
}
var base = GetBase(value);
if ( base === null) {
throw new ReferenceError;
}
return base.[[Get]](GetPropertyName(value));
}
The internal [[Get]] method returns the real value of the object's properties, including analysis of inherited properties in the prototype chain.
Copy code The code is as follows:
GetValue(fooReference); // 10
GetValue(barReference); // function object "bar"
Attribute accessors should all be familiar. It has two variants: dot (.) syntax (when the property name is the correct identifier and is known in advance), or bracket syntax ([]).
foo.bar();
foo[' bar']();
In the return value of the intermediate calculation, we have the value of the reference type.
var fooBarReference = {
base: foo,
propertyName: 'bar'
};
GetValue(fooBarReference); // function object "bar"
The value of the reference type and the this value in the function context How is it relevant? — in the most important sense. This process of association is at the heart of this article. The general rules for determining the value of this in a function context are as follows:
In a function context, this is provided by the caller and is determined by the way the function is called. If the left side of the calling bracket () is a value of reference type, this will be set to the base object of the reference type value. In other cases (any other properties different from the reference type), this value will be null. However, there is no actual situation where the value of this is null, because when the value of this is null, its value will be implicitly converted to the global object. Note: In the fifth edition of ECMAScript, conversion to global variables is no longer forced, but is assigned to undefined.
Let’s take a look at the performance in this example:
function foo() {
return this;
}
foo(); // global
We see that to the left of the calling bracket is a Reference type value (because foo is an identifier).
var fooReference = {
base: global,
propertyName: 'foo'
};
Correspondingly, this is also set to the base object of the reference type. That is the global object.
Similarly, use the property accessor:
var foo = {
bar: function () {
return this;
}
};
foo.bar(); // foo
We again have a reference type whose base is the foo object, which is used as this when function bar is activated.
var fooBarReference = {
base: foo,
propertyName: 'bar'
};
However, activating the same function in another form, we get other this values.
var test = foo.bar;
test( ); // global
Because test is used as an identifier, other values of the reference type are generated, and its base (global object) is used as this value.
var testReference = {
base: global,
propertyName: 'test'
};
Now, we can clearly tell you why activating the same function with different forms of expressions will have different this values. The answer is Intermediate values with different reference types (type Reference).
function foo() {
alert(this);
}
foo(); // global, because
var fooReference = {
base: global,
propertyName: 'foo'
};
alert(foo === foo.prototype.constructor); // true
// Another one A form of calling expression
foo.prototype.constructor(); // foo.prototype, because
var fooPrototypeConstructorReference = {
base: foo.prototype,
propertyName : 'constructor'
};
Another classic example of dynamically determining the value of this by calling:
function foo() {
alert(this.bar);
}
var x = {bar: 10};
var y = {bar: 20};
x.test = foo;
y.test = foo;
x.test(); // 10
y.test(); // 20
Function calls and non-reference types
So, as we have pointed out, when the left side of the calling bracket is not a reference type but other types , this value is automatically set to null, and the result is a global object.
Let us think about this expression again:
(function () {
alert(this); // null => global
})();
In this example, we have a function object But it is not an object of reference type (it is not an identifier or a property accessor). Accordingly, the this value is ultimately set to the global object.
More complex examples:
var foo = {
bar: function () {
alert(this);
}
};
foo.bar(); // Reference, OK => foo
(foo.bar)(); // Reference, OK => foo
(foo.bar = foo.bar)(); // global?
(false || foo.bar)(); // global?
(foo.bar, foo.bar)(); // global?
Why do we have a property accessor in the middle of it The value should be a reference type value. In some calls, the this value we get is not the base object, but the global object?
The problem is that in the following three calls, after applying certain operations, the value on the left side of the calling bracket is no longer a reference type.
The first example is obvious - an obvious reference type. The result is that this is the base object, which is foo.
In the second example, the group operator does not apply. Think of the methods mentioned above that obtain the actual value of an object from a reference type, such as GetValue. Correspondingly, in the return of the group operation - we still get a reference type. This is why the this value is set to the base object again, which is foo.
In the third example, unlike the group operator, the assignment operator calls the GetValue method. The returned result is a function object (but not a reference type), which means this is set to null and the result is a global object.
The same goes for the fourth and fifth ones - the comma operator and the logical operator (OR) call the GetValue method, and accordingly, we lose the reference and get the function. And set it to global again.
Reference type and this is null
There is a situation like this: when the calling expression limits the value of the reference type on the left side of the call bracket, although this is set to null, the result is converted to global. This situation occurs when the base object of a reference type value is the active object.
In the following example, the internal function is called by the parent function. At this time, we can see the special situation mentioned above. As we learned in Chapter 12, local variables, internal functions, and formal parameters are stored in the activation object of a given function.
function foo() {
function bar() {
alert(this); // global
}
bar(); // the same as AO.bar()
}
The active object is always returned as this, and the value is null - (that is, AO.bar() in pseudo code is equivalent to null.bar()). Here we return to the example described above again, with this set to the global object.
There is one exception: if the with object contains a function name attribute, the function is called in the inner block of the with statement. The With statement is added to the front of the object's scope, that is, in front of the active object. Correspondingly, there are reference types (through identifiers or property accessors) whose base object is no longer the active object, but the object of the with statement. By the way, it is not only relevant to internal functions, but also to global functions, because the with object is earlier than the frontmost object in the scope chain (the global object or an active object).
var x = 10;
with ({
foo: function () {
alert(this.x);
},
x: 20
}) {
foo(); // 20
}
// because
var fooReference = {
base: __withObject,
propertyName: 'foo'
};
The same situation occurs in the actual parameters of the catch statement function call: in this case, the catch object is added to the front of the scope, that is, in the active object or global object Front. However, this specific behavior was confirmed to be a bug in ECMA-262-3, which is fixed in the new version of ECMA-262-5. In this way, in a specific active object, this points to the global object. rather than the catch object.
try {
throw function () {
alert(this);
};
} catch (e) {
e(); // ES3 standard is __catchObject, ES5 standard is global
}
// on idea
var eReference = {
base: __catchObject,
propertyName: 'e'
};
// This has been fixed in the new ES5 standard This bug,
// So this is the global object
var eReference = {
base: global,
propertyName: 'e'
};
The same situation occurs with recursive calls to named functions (see Chapter 15 Functions for more details on functions). In the first call of the function, the base object is the parent activity object (or global object). In recursive calls, the base object should be a specific object that stores the optional name of the function expression. However, in this case, this always points to the global object.
(function foo(bar) {
alert(this);
!bar && foo(1); // "should" be special object, but always (correct) global
})(); // global
This in a function called as a constructor
Another situation related to this value is in the context of a function, which is a call to a constructor.
function A() {
alert(this) ; // Create a new attribute under the "a" object
this.x = 10;
}
var a = new A();
alert(a.x); // 10
In this example, the new operator calls the internal [[Construct]] method of the "A" function, and then, after the object is created, calls the internal [[Call]] method. All the same function "A" sets the value of this to the newly created object.
Manually setting this in function calls
Two methods defined in the function prototype (so all functions can access it) allow to manually set the this value in function calls. They are .apply and .call methods. They use the first argument they accept as the this value, and this is used in the calling scope. The difference between these two methods is very small. For .apply, the second parameter must be an array (or an array-like object, such as arguments. In turn, .call can accept any parameter. The required parameter for both methods is the first ——this.
For example:
var b = 10;
function a(c) {
alert(this.b);
alert(c);
}
a(20) ; // this === global, this.b == 10, c == 20
a.call({b: 20}, 30); // this === {b: 20} , this.b == 20, c == 30
a.apply({b: 30}, [40]) // this === {b: 30}, this.b == 30, c = = 40
Conclusion
In this article, we discussed the characteristics of this keyword in ECMAScript (compared to C and Java, they are indeed characteristics). I hope this article helped you understand exactly how the this keyword works in ECMAScript. Again, I'd be happy to get back to your question in the comments.
Other references