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:

  1. when we deal with an identifier;
  2. 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), thisvalue 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 thisvalue 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

你可能感兴趣的:(this)