ECMA-262-3 in detail. Chapter 3. This.
转载地址:http://dmitrysoshnikov.com/ecmascript/chapter-3-this/
Introduction
In this article we will discuss one more detail directly related with execution contexts. The topic of discussion is the this
keyword.
As the practice shows, this topic is difficult enough and often causes issues in determination ofthis
value in different execution contexts.
Many programmers are used to thinking that the this
keyword in programming languages is closely related to the object-oriented programming, exactly referring the newly created object by the constructor. In ECMAScript this concept is also implemented, however, as we will see, here it is not limited only to definition of created object.
Let’s see in detail what exactly this
value is in ECMAScript.
Definitions
this
value is a property of the execution context:
activeExecutionContext = { VO: {...}, this : thisValue }; |
where VO is variable object which we discussed in the previous chapter.
this
is directly related to the type of executable code of the context. The value is determined on entering the context and is immutable while the code is running in the context.
Let’s consider these cases more in detail.
This value in the global code
Here everything is simple enough. In the global code, this
value is always the global object itself. Thus, it is possible to reference it indirectly:
// explicit property definition of // the global object this .a = 10; // global.a = 10 alert(a); // 10 // implicit definition via assigning // to unqualified identifier b = 20; alert( this .b); // 20 // also implicit via variable declaration // because variable object of the global context // is the global object itself var c = 30; alert( this .c); // 30 |
This value in the function code
Things are more interesting when this
is used in function code. This case is the most difficult and causes many issues.
The first (and, probably, the main) feature of this
value in this type of code is that here it is not statically bound to a function.
As it has been mentioned above, this
value is determined on entering the context, and in case with a function code the value can be absolutely different every time.
However, at runtime of the code this
value is immutable, i.e. it is not possible to assign a new value to it since this is not a variable (in contrast, say, with Python programming language and its explicitly defined self
object which can repeatedly be changed at runtime):
var foo = {x: 10}; var bar = { x: 20, test: function () { alert( this === bar); // true alert( this .x); // 20 this = foo; // error, can't change this value alert( this .x); // if there wasn't an error, then would be 10, not 20 } }; // on entering the context this value is // determined as "bar" object; why so - will // be discussed below in detail bar.test(); // true, 20 foo.test = bar.test; // however here this value will now refer // to "foo" – even though we're calling the same function foo.test(); // false, 10 |
So what affects the variations of this
value in function code? There are several factors.
First, in a usual function call, this
is provided by the caller which activates the code of the context, i.e. the parent context which calls the function. And the value of this
is determined by the form of a call expression (in other words by the form how syntactically the function is called).
It is necessary to understand and remember this important point in order to be able to determinethis
value in any context without any problems. Exactly the form of a call expression, i.e. the way of calling the function, influences this
value of a called context and nothing else.
(as we can see in some articles and even books on JavaScript which claim that “this
value depends on how function is defined: if it is global function then this
value is set to global object, if function is a method of an object this
value is always set to this object” — what is mistaken description). Moving forward, we see that even normal global functions can be activated with different forms of a call expression which influence a different this
value:
function foo() { alert( this ); } foo(); // global alert(foo === foo.prototype.constructor); // true // but with another form of the call expression // of the same function, this value is different foo.prototype.constructor(); // foo.prototype |
It is similarly possible to call the function defined as a method of some object, but this
value 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 // again with another form of the call expression // of the same function, we have different this value exampleFunc(); // global, false |
So how does the form of the call expression influences this
value? In order to fully understand the determination of the this
value, it’s necessary to consider in detail one of the internal types — theReference
type.
Reference type
Using pseudo-code the value of Reference
type can be represented as an object with two properties: base (i.e. object to which a property belongs) and a propertyName in this base:
var valueOfReferenceType = { base: <base object>, propertyName: <property name> }; |
Value of Reference
type can be only in two cases:
- when we deal with an identifier;
- or with a property accessor.
Identifiers are handled by the process of identifiers resolution which is in detail considered in theChapter 4. Scope chain. And here we just notice that at return from this algorithm always there is a value of Reference
type (it is important for this
value).
Identifiers are variable names, function names, names of function arguments and names of unqualified properties of the global object. For example, for values on following identifiers:
var foo = 10; function bar() {} |
in intermediate results of operations, corresponding values of Reference
type are the following:
var fooReference = { base: global, propertyName: 'foo' }; var barReference = { base: global, propertyName: 'bar' }; |
For getting the real value of an object from a value of Reference
type there is GetValue
method which in a pseudo-code 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)); } |
where the internal [[Get]]
method returns the real value of object’s property, including as well analysis of the inherited properties from a prototype chain:
GetValue(fooReference); // 10 GetValue(barReference); // function object "bar" |
Property accessors are also know; there are two variations: the dot notation (when the property name is correct identifier and is in advance known), or the bracket notation:
foo.bar(); foo[ 'bar' ](); |
On return of intermediate calculation we also have the value of Reference
type:
var fooBarReference = { base: foo, propertyName: 'bar' }; GetValue(fooBarReference); // function object "bar" |
So, how a value of Reference
type is related with this
value of a function context? — in the most important sense. The given moment is the main of this article. The general rule of determination ofthis
value in a function context sounds as follows:
The value of this
in a function context is provided by the caller and determined by the current form of a call expression (how the function call is written syntactically).
If on the left hand side from the call parentheses ( ... )
, there is a value of Reference
type then this
value is set to the base object of this value of Reference
type.
In all other cases (i.e. with any other value type which is distinct from the Reference
type), this
value is always set to null
. But since there is no any sense in null
for this
value, it is implicitlyconverted to global object.
Let’s show on examples:
function foo() { return this ; } foo(); // global |
We see that on the left hand side of call parentheses there is a Reference
type value (because foois an identifier):
var fooReference = { base: global, propertyName: 'foo' }; |
Accordingly, this
value is set to base object of this value of Reference
type, i.e. to global object.
Similarly with the property accessor:
var foo = { bar: function () { return this ; } }; foo.bar(); // foo |
Again we have the value of type Reference
which base is foo
object and which is used as this
value at bar
function activation:
var fooBarReference = { base: foo, propertyName: 'bar' }; |
However, activating the same function with another form of a call expression, we have already otherthis
value:
var test = foo.bar; test(); // global |
because test
, being the identifier, produces other value of Reference
type, which base (the global object) is used as this
value:
var testReference = { base: global, propertyName: 'test' }; |
Note, in the strict mode of ES5 this
value is not coerced to global object, but instead is set to undefined
.
Now we can precisely tell, why the same function activated with different forms of a call expression, has also different this
values — the answer is in different intermediate values of type Reference
:
function foo() { alert( this ); } foo(); // global, because var fooReference = { base: global, propertyName: 'foo' }; alert(foo === foo.prototype.constructor); // true // another form of the call expression foo.prototype.constructor(); // foo.prototype, because var fooPrototypeConstructorReference = { base: foo.prototype, propertyName: 'constructor' }; |
Another (classical) example of dynamic determination of this
value by the form of a call expression:
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 call and non-Reference type
So, as we have noted, in case when on the left hand side of call parentheses there is a value not ofReference
type but any another type, this
value is automatically set to null
and, as consequence, to the global object.
Let’s consider examples of such expressions:
( function () { alert( this ); // null => global })(); |
In this case, we have function object but not object of Reference
type (it is not the identifier and not the property accessor), accordingly this
value finally is set to 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? |
So, why having a property accessor which intermediate result should be a value of Reference
type, in certain calls we get for this
value not the base object (i.e. foo
) but global?
The matter is that last three calls, after applying of certain operations, have already on the left hand side of call parentheses the value not of Reference type.
With the first case all is clear – there unequivocally Reference
type and, as consequence, this
value is the base object, i.e. foo
.
In the second case there is a grouping operator which does not apply, considered above, method of getting the real value of an object from value of Reference
type, i.e. GetValue
(see note of 11.1.6). Accordingly, at return from evaluation of the grouping operator — we still have a value ofReference
type and that is why this
value is again set to the base object, i.e. foo
.
In the third case, assignment operator, unlike the grouping operator, calls GetValue
method (see step 3 of 11.13.1). As a result at return there is already function object (but not a value ofReference
type) which means that this
value set to null
and, as consequence, to global.
Similarly with the fourth and fifth cases — the comma operator and logical OR expression call theGetValue
method and accordingly we lose value of type Reference
and get value of type function; and again this
value is set to global.
Reference type and null this value
There is a case when call expression determines on the left hand side of call parentheses the value of Reference
type, however this
value is set to null
and, as consequence, to global. It is related to the case when the base object of Reference
type value is the activation object.
We can see this situation on an example with the inner function called from the parent. As we know from the second chapter, local variables, inner functions and formal parameters are stored in theactivation object of the given function:
function foo() { function bar() { alert( this ); // global } bar(); // the same as AO.bar() } |
The activation object always returns as this
value — null
(i.e. pseudo-code AO.bar()
is equivalent to null.bar()
). Here again we come back to the described above case, and again, this
value is set to global object.
The exception can be with a function call inside the block of the with
statement in case if withobject contains a function name property. The with
statement adds its object in front of scope chain i.e. before the activation object. Accordingly, having values of type Reference
(by the identifier or a property accessor) we have base object not as an activation object but object of a with
statement. By the way, it relates not only to inner, but also to global functions because the with
object shadows higher object (global or an activation object) of the scope chain:
var x = 10; with ({ foo: function () { alert( this .x); }, x: 20 }) { foo(); // 20 } // because var fooReference = { base: __withObject, propertyName: 'foo' }; |
The similar situation should be with calling of the function which is the actual parameter of thecatch
clause: in this case the catch
object is also added in front of scope chain i.e. before the activation or global object. However, the given behavior was recognized as a bug of ECMA-262-3 and is fixed in the new version of standard — ECMA-262-5. I.e. this
value in the given activation should be set to global object, but not to catch
object:
try { throw function () { alert( this ); }; } catch (e) { e(); // __catchObject - in ES3, global - fixed in ES5 } // on idea var eReference = { base: __catchObject, propertyName: 'e' }; // but, as this is a bug // then this value is forced to global // null => global var eReference = { base: global, propertyName: 'e' }; |
The same situation with a recursive call of the named function expression (more detailed about functions see in Chapter 5. Functions). At the first call of function, base object is the parent activation object (or the global object), at the recursive call — base object should be special object storing the optional name of a function expression. However, in this case this
value is also always set to global:
( function foo(bar) { alert( this ); !bar && foo(1); // "should" be special object, but always (correct) global })(); // global |
This value in function called as the constructor
There is one more case related with this
value in a function context — it is a call of function as the constructor:
function A() { alert( this ); // newly created object, below - "a" object this .x = 10; } var a = new A(); alert(a.x); // 10 |
In this case, the new operator calls the internal [[Construct]] method of the A
function which, in turn, after object creation, calls the internal [[Call]] method, all the same function A
, having provided as this
value newly created object.
Manual setting of this value for a function call
There are two methods defined in the Function.prototype
(therefore they are accessible to all functions), allowing to specify this
value of a function call manually. These are apply
and call
methods.
Both of them accept as the first argument this
value which is used in a called context. A difference between these methods is insignificant: for the apply
the second argument necessarily should be an array (or, the array-like object, for example, arguments
), in turn, the call
method can accept any arguments; obligatory arguments for both methods is only the first — this
value.
Examples:
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 have discussed features of the this
keyword in ECMAScript (and they really arefeatures, in contrast, say, with C++ or Java). I hope article helped to understand more accurately how this
keyword works in ECMAScript. As always, I am glad to answer your questions in comments.
Additional literature
10.1.7 – This;
11.1.1 – The this keyword;
11.2.2 – The new operator;
11.2.3 – Function calls.