`
三月沙
  • 浏览: 617891 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

ECMA-262-3 in detail. Chapter 3. This

 
阅读更多

 by Dmitry Soshnikov

 

 

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 of this 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.

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.

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

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 definedself 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 determine thisvalue 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.

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 the Chapter 4. Scope chain. And here we just notice that at return from this algorithm always there is a value of Referencetype (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 of this 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 thisvalue 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 implicitly converted toglobal 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 foo is 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 atbar function activation:

var fooBarReference = {
  base: foo,
  propertyName: 'bar'
};

However, activating the same function with another form of a call expression, we have already other thisvalue:

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 toundefined.

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

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 theglobal 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 of Reference 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 of11.13.1). As a result at return there is already function object (but not a value of Reference 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.

There is a case when call expression determines on the left hand side of call parentheses the value ofReference 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 thesecond chapter, local variables, inner functions and formal parameters are stored in the activation 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 tonull.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 with object contains a function name property. The with statement adds its object in front of scope chain i.e. beforethe 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 the catchclause: 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

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.

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, thearray-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

In this article we have discussed features of the this keyword in ECMAScript (and they really are features, 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.

 

 

中文版地址 http://www.denisdeng.com/?p=900

 

 

  1. 导言
  2. 定义
  3. 全局代码中的this
  4. 函数代码中的this
    1. 引用类型
    2. 函数调用和非引用类型
    3. 引用类型和this为null
    4. 作为构造器调用的函数的this
    5. 手动设置一个函数调用的this
  5. 结论
  6. 其他参考

导言

在这篇文章中我们将讨论一个与执行上下文直接相关的更多细节。讨论的主题就是this关键字。

实践表明,这个主题很难,在不同执行上下文中this值的确定经常导致问题。

许多程序员习惯的认为,在程序语言中,this关键字与面向对象的程序紧密相关,完全指向通过构造器创建的新的对象。在ECMAScript中也是这样执行的,但正如你看到的那样,这并不限于创建对象的定义。

让我们更详细的了解ECMAScript中真正的this值是什么?

定义

this是执行上下文中的一个属性。

1.activeExecutionContext = {
2.VO: {...},
3.this: thisValue
4.};

这里VO是我们前一章讨论的变量对象

this与上下文中可执行代码密切直接相关,this值在进入上下文时确定,代码在上下文运行期间一成不变

下面让我们更详细研究这些案例

全局代码中的this值

在这里一切都简单。在全局代码中,this始终是全局对象本身,这样有可能间接的引用它。

01.// explicit property definition of
02.// the global object
03.this.a = 10;// global.a = 10
04.alert(a);// 10
05.// implicit definition via assigning
06.// to unqualified identifier
07.b = 20;
08.alert(this.b);// 20
09.// also implicit via variable declaration
10.// because variable object of the global context
11.// is the global object itself
12.var c = 30;
13.alert(this.c);// 30

函数代码中的this值

在函数代码中使用this 时很有趣,这种情况很难且会导致很多问题。

这种类型的代码中,this值的首要特点(或许是最主要的)是它不是静态的绑定到一个函数

正如我们上面曾提到的那样,这个值进入上下文时确定,在一个函数代码中,这个值在每一次完全不同。

但是,在代码运行时的this值是不变的,也就是说,既然它不是一个变量,就不可能为其分配一个新值(相反,在Python编程语言中,它明确的定义为对象本身,在运行期间可以不断改变)。

01.var foo = {x: 10};
02.var bar = {
03.x: 20,
04.test: function () {
05.alert(this === bar);// true
06.alert(this.x);// 20
07.this = foo;// error
08.alert(this.x);// if there wasn't an error then 20, not 10
09. 
10.}
11.};
12.// on entering the context this value is
13.// determined as "bar" object; why so - will
14.// be discussed below in detail
15.bar.test();// true, 20
16.foo.test = bar.test;
17.// however here this value will now refer
18.// to "foo" – even though we're calling the same function
19.foo.test();// false, 10

那么,是什么影响了函数代码中this值的变化。有几个因素:

首先,在通常的函数调用中,this是由激活上下文代码的调用者来提供的,即调用函数的父上下文(parent context )。this取决于调用函数的方式

为了在任何情况下准确无误的确定this值,有必要理解和记住这重要的一点。正是调用函数的方式影响了调用的上下文中的this值,没有别的什么(我们可以在一些文章,甚至是在关于javascript的书籍中看到,它们声称:“this值取决于函数如何定义,如果它是全局函数,this设置为全局对象,如果函数是一个对象的方法,this将总是指向这个对象。–这绝对不正确”)。继续我们的话题,可以看到,即使是正常的全局函数也会被调用方式的不同形式激活,这些不同的调用方式导致了不同的this值。

1.function foo() {
2.alert(this);
3.}
4.foo();// global
5.alert(foo === foo.prototype.constructor);// true
6.// but with another form of the call expression
7.// of the same function, this value is different
8.foo.prototype.constructor();// foo.prototype

有可能作为一些对象定义的方法来调用函数,但是this将不会设置为这个对象。

01.var foo = {
02.bar: function () {
03.alert(this);
04.alert(this === foo);
05.}
06.};
07.foo.bar();// foo, true
08.var exampleFunc = foo.bar;
09.alert(exampleFunc === foo.bar);// true
10.// again with another form of the call expression
11.// of the same function, we have different this value
12.exampleFunc();// global, false

那么,调用函数的方式如何影响this值?为了充分理解this值的确定,需要详细分析其内部属性之一——引用类型(Reference type)。

引用类型

使用伪代码,引用类型的值可以表示为拥有两个属性的对象——base(即拥有属性的那个对象),和base中的propertyName 。

1.var valueOfReferenceType = {
2.base: <base object>,
3.propertyName: <property name>
4.};

引用类型的值只有两种情况:

  1. 当我们处理一个标示符时
  2. 或一个属性访问器

标示符的处理过程在Chapter 4. Scope chain 中讨论,在这里我们只是看到,在该算法的返回值中,总是一个引用类型的值(这对this来说很重要)。

标识符是变量名,函数名,函数参数名和全局对象中未识别的属性名。例如,下面标识符的值:

1.var foo = 10;
2.function bar() {}

在操作的中间结果中,引用类型对应的值如下:

01.var fooReference = {
02.base: global,
03.propertyName: 'foo'
04.};
05. 
06.var barReference = {
07.base: global,
08.propertyName: 'bar'
09.};

为了从引用类型中得到一个对象真正的值,伪代码中的GetValue方法可以做如下描述:

01.function GetValue(value) {
02.if (Type(value) != Reference) {
03.return value;
04.}
05.var base = GetBase(value);
06.if (base === null) {
07.throw new ReferenceError;
08.}
09.return base.[[Get]](GetPropertyName(value));
10.}

在那里内部的[[Get]]方法返回对象属性真正的值,包括对原型链中继承的属性分析。

1.GetValue(fooReference);// 10
2.GetValue(barReference);// function object "bar"

属性访问器应该熟悉。它有两种变体:点(.)语法(此时属性名是正确的标示符,且事先知道),或括号语法([])。

1.foo.bar();
2.foo['bar']();

在中间计算的返回值中,我们有了引用类型的值。

引用类型的值与函数上下文中的this值如何相关?——从最重要的意义上来说。 这个关联的过程是这篇文章的核心。 一个函数上下文中确定this值的通用规则如下:

在一个函数上下文中,this由调用者提供,由调用函数的方式来决定。如果调用括号()的左边是引用类型的值,this将设为引用类型值的base对象(base object),在其他情况下(与引用类型不同的任何其它属性),这个值为null。不过,实际不存在this的值为null的情况,因为当this的值为null的时候,其值会被隐式转换为全局对象。

我们看看这个例子中的表现:

1.function foo() {
2.return this;
3.}
4.foo();// global

我们看到在调用括号的左边是一个引用类型值(因为foo是一个标示符)。

1.var fooReference = {
2.base: global,
3.propertyName: 'foo'
4.};

相应地,this也设置为引用类型的base对象。即全局对象。

同样,使用属性访问器:

1.var foo = {
2.bar: function () {
3.return this;
4.}
5.};
6.foo.bar();// foo

我们再次拥有一个引用类型,其base是foo对象,在函数bar激活时用作this

1.var fooBarReference = {
2.base: foo,
3.propertyName: 'bar'
4.};

但是,用另外一种形式激活相同的函数,我们得到其它的this值。

1.var test = foo.bar;
2.test();// global

因为test作为标示符,生成了引用类型的其他值,其base(全局对象)用作this 值。

1.var testReference = {
2.base: global,
3.propertyName: 'test'
4.};

现在,我们可以很明确的告诉你,为什么用表达式的不同形式激活同一个函数会不同的this值,答案在于引用类型(type Reference)不同的中间值。

01.function foo() {
02.alert(this);
03.}
04. 
05.foo();// global, because
06. 
07.var fooReference = {
08.base: global,
09.propertyName: 'foo'
10.};
11. 
12.alert(foo === foo.prototype.constructor);// true
13. 
14.// another form of the call expression
15. 
16.foo.prototype.constructor();// foo.prototype, because
17. 
18.var fooPrototypeConstructorReference = {
19.base: foo.prototype,
20.propertyName: 'constructor'
21.};

另外一个通过调用方式动态确定this值的经典例子:

01.function foo() {
02.alert(this.bar);
03.}
04. 
05.var x = {bar: 10};
06.var y = {bar: 20};
07. 
08.x.test = foo;
09.y.test = foo;
10. 
11.x.test();// 10
12.y.test();// 20

函数调用和非引用类型

因此,正如我们已经指出,当调用括号的左边不是引用类型而是其它类型,这个值自动设置为null,结果为全局对象。

让我们再思考这种表达式:

1.(function  () {
2.alert(this);// null => global
3.})();

在这个例子中,我们有一个函数对象但不是引用类型的对象(它不是标示符,也不是属性访问器),相应地,this值最终设为全局对象。

更多复杂的例子:

01.var foo = {
02.bar: function () {
03.alert(this);
04.}
05.};
06. 
07.foo.bar();// Reference, OK => foo
08.(foo.bar)();// Reference, OK => foo
09. 
10.(foo.bar = foo.bar)();// global?
11.(false || foo.bar)();// global?
12.(foo.bar, foo.bar)();// global?

为什么我们有一个属性访问器,它的中间值应该为引用类型的值,在某些调用中我们得到的this值不是base对象,而是global对象?

问题在于后面的三个调用,在应用一定的运算操作之后,在调用括号的左边的值不在是引用类型。

第一个例子很明显———明显的引用类型,结果是,this为base对象,即foo。

在第二个例子中,组运算符并不适用,想想上面提到的,从引用类型中获得一个对象真正的值的方法,如GetValue。相应的,在组运算的返回中———我们得到仍是一个引用类型。这就是this值为什么再次设为base对象,即foo。

第三个例子中,与组运算符不同,赋值运算符调用了GetValue方法。返回的结果是函数对象(但不是引用类型),这意味着this设为null,结果是global对象。

第四个和第五个也是一样——逗号运算符和逻辑运算符(OR)调用了GetValue 方法,相应地,我们失去了引用而得到了函数。并再次设为global。

引用类型和 this为null

有一种情况是这样的:当调用表达式限定了call括号左边的引用类型的值, 尽管this被设定为null,但结果是global。当引用类型值的base对象是被激活的对象时,这种情况就会出现。

下面的实例中,内部函数被父函数调用,此时我们就能够看到上面说的那种特殊情况。正如我们在第二章知道的一样,局部变量、内部函数、形式参数储存在给定函数的激活对象中。

1.function foo() {
2.function bar() {
3.alert(this);// global
4.}
5.bar();// the same as AO.bar()
6.}

激活对象总是作为this返回,值为null——(即伪代码的AO.bar()相当于null.bar())。这里我们再次回到上面描述的例子,this设置为全局对象。

有一种情况除外:如果with对象包含一个函数名属性,在with语句的内部块中调用函数。With语句添加到该对象作用域的最前端,即在激活对象的前面。相应地,也就有了引用类型(通过标示符或属性访问器), 其base对象不再是激活对象,而是with语句的对象。顺便提一句,它不仅与内部函数相关,也与全局函数相关。因为with对象使作用域最前端的对象(全局或激活对象)相形见绌。

01.var x = 10;
02.with ({
03. 
04.foo: function () {
05.alert(this.x);
06.},
07.x: 20
08. 
09.}) {
10. 
11.foo();// 20
12. 
13.}
14. 
15.// because
16. 
17.var  fooReference = {
18.base: __withObject,
19.propertyName: 'foo'
20.};

同样的情况出现在catch语句的实际参数中函数调用:在这种情况下,catch对象添加到作用域的最前端,即在激活对象或全局对象的前面。但是,这个特定的行为被确认为ECMA-262-3的一个bug,这个在新版的ECMA-262-5中修复了。这样,在特定的激活对象中,this指向全局对象。而不是catch对象。

01.try {
02.throw function () {
03.alert(this);
04.};
05.catch (e) {
06.e();// __catchObject - in ES3, global - fixed in ES5
07.}
08.// on idea
09.var eReference = {
10.base: __catchObject,
11.propertyName: 'e'
12.};
13.// but, as this is a bug
14.// then this value is forced to global
15.// null => global
16.var eReference = {
17.base: global,
18.propertyName: 'e'
19.};

同样的情况出现在命名函数(函数的更对细节参考Chapter 5. Functions)的递归调用中。在函数的第一次调用中,base对象是父激活对象(或全局对象),在递归调用中,base对象应该是存储着函数表达式可选名称的特定对象。但是,在这种情况下,this总是指向全局对象。

1.(function  foo(bar) {
2. 
3.alert(this);
4. 
5.!bar && foo(1);// "should" be special object, but always (correct) global
6. 
7.})();// global

作为构造器调用的函数的this

还有一个与this值相关的情况是在函数的上下文中,这是一个构造函数的调用。

1.function A() {
2.alert(this);// newly created object, below - "a" object
3.this.x = 10;
4.}
5. 
6.var a = new A();
7.alert(a.x);// 10

在这个例子中,new运算符调用“A”函数的内部的[[Construct]] 方法,接着,在对象创建后,调用内部的[[Call]] 方法。 所有相同的函数“A”都将this的值设置为新创建的对象。

手动设置一个函数调用的this

在函数原型中定义的两个方法(因此所有的函数都可以访问它)允许去手动设置函数调用的this值。它们是.apply和.call方法。他们用接受的第一个参数作为this值,this 在调用的作用域中使用。这两个方法的区别很小,对于.apply,第二个参数必须是数组(或者是类似数组的对象,如arguments,反过来,.call能接受任何参数。两个方法必须的参数是第一个——this

例如:

01.var b = 10;
02.function a(c) {
03.alert(this.b);
04.alert(c);
05.}
06. 
07.a(20);// this === global, this.b == 10, c == 20
08. 
09.a.call({b: 20}, 30);// this === {b: 20}, this.b == 20, c == 30
10.a.apply({b: 30}, [40])// this === {b: 30}, this.b == 30, c == 40

结论

在这篇文章中,我们讨论了ECMAScript中this关键字的特征(对比于C++ 和 Java,它们的确是特色)。我希望这篇文章有助于你准确的理解ECMAScript中this关键字如何工作。同样,我很乐意在评论中回到你的问题。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics