标示符的处理过程在下一篇文章里详细讨论,在这里我们只需要知道,在该算法的返回值中,总是一个引用类型的值(这对this来说很重要)。
标识符是变量名,函数名,函数参数名和全局对象中未识别的属性名。例如,下面标识符的值:
var foo = 10;function bar() {}
在操作的中间结果中,引用类型对应的值如下:
var fooReference = { base: global, propertyName: 'foo'}; var barReference = { base: global, propertyName: 'bar'};
为了从引用类型中得到一个对象真正的值,伪代码中的GetValue方法可以做如下描述:
function GetValue(value) { if (Type(value) != Reference) { return value; } var base = GetBase(value); if (base === null) { throw new ReferenceError; } return base.[[Get]](GetPropertyName(value)); }
内部的[[Get]]方法返回对象属性真正的值,包括对原型链中继承的属性分析。
GetValue(fooReference); // 10GetValue(barReference); // function object "bar"
属性访问器都应该熟悉。它有两种变体:点(.)语法(此时属性名是正确的标示符,且事先知道),或括号语法([])。
foo.bar();foo['bar']();
在中间计算的返回值中,我们有了引用类型的值。
var fooBarReference = { base: foo, propertyName: 'bar'}; GetValue(fooBarReference); // function object "bar"
引用类型的值与函数上下文中的this值如何相关?——从最重要的意义上来说。 这个关联的过程是这篇文章的核心。 一个函数上下文中确定this值的通用规则如下:
在一个函数上下文中,this由调用者提供,由调用函数的方式来决定。如果调用括号()的左边是引用类型的值,this将设为引用类型值的base对象(base object),在其他情况下(与引用类型不同的任何其它属性),这个值为null。不过,实际不存在this的值为null的情况,因为当this的值为null的时候,其值会被隐式转换为全局对象。注:第5版的ECMAScript中,已经不强迫转换成全局变量了,而是赋值为undefined。
我们看看这个例子中的表现:
function foo() { return this;} foo(); // global
我们看到在调用括号的左边是一个引用类型值(因为foo是一个标示符)。
var fooReference = { base: global, propertyName: 'foo'};
相应地,this也设置为引用类型的base对象。即全局对象。
同样,使用属性访问器:
var foo = { bar: function () { return this; }}; foo.bar(); // foo
我们再次拥有一个引用类型,其base是foo对象,在函数bar激活时用作this。
var fooBarReference = { base: foo, propertyName: 'bar'};
但是,用另外一种形式激活相同的函数,我们得到其它的this值。
var test = foo.bar;test(); // global
因为test作为标示符,生成了引用类型的其他值,其base(全局对象)用作this 值。
var testReference = { base: global, propertyName: 'test'};
现在,我们可以很明确的告诉你,为什么用表达式的不同形式激活同一个函数会不同的this值,答案在于引用类型(type Reference)不同的中间值。
function foo() { alert(this);} foo(); // global, because var fooReference = { base: global, propertyName: 'foo'}; alert(foo === foo.prototype.constructor); // true // 另外一种形式的调用表达式 foo.prototype.constructor(); // foo.prototype, because var fooPrototypeConstructorReference = { base: foo.prototype, propertyName: 'constructor'};
另外一个通过调用方式动态确定this值的经典例子:
function foo() { alert(this.bar);} var x = {bar: 10};var y = {bar: 20}; x.test = foo;y.test = foo; x.test(); // 10y.test(); // 20
函数调用和非引用类型
因此,正如我们已经指出,当调用括号的左边不是引用类型而是其它类型,这个值自动设置为null,结果为全局对象。
让我们再思考这种表达式:
(function () { alert(this); // null => global})();
在这个例子中,我们有一个函数对象但不是引用类型的对象(它不是标示符,也不是属性访问器),相应地,this值最终设为全局对象。
更多复杂的例子:
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?
为什么我们有一个属性访问器,它的中间值应该为引用类型的值,在某些调用中我们得到的this值不是base对象,而是global对象?
问题在于后面的三个调用,在应用一定的运算操作之后,在调用括号的左边的值不在是引用类型。
1.第一个例子很明显———明显的引用类型,结果是,this为base对象,即foo。