types
在讲 js 的this指向问题之前,我们先讲一下 js 中的 types,因为按照 ECMA规范中的描述,this的指向会涉及到这些概念
在ECMAScript中,类型分为语言类型和规范类型两种。
语言类型就是我们开发中经常使用的类型,也就是Null,Undefined,Object,Boolean,String,Number,Sysbol等类型。
而规范类型就相当于meta-values,是用来用算法来描述ECMAScript语言结构和ECMAScript语言类型的,规范类型包括Reference、List、Completion、Property Descriptor、Property Identifler、Lexical Enviroment和Enviroment Record。
这里我们要知道ECMAScript规范中还有一种只存在于规范中的类型,它们的作用就是用来描述语言底层行为逻辑的。而其中的Reference类型就是与this的指向有着密切关联的
Reference
1、什么是Reference?
ECMAScript规范中,8.7章 The Reference Specification Type:
The Reference type is used to explain the behaviour of such operators as delete, typeof, and the assignment operato
所以Reference类型就是用来解释诸如 delete、typeof以及赋值等操作行为的。
用尤雨溪的话就是:
这里的 Reference 是一个 Specification Type,也就是 “只存在于规范里的抽象类型”。它们是为了更好地描述语言的底层行为逻辑才存在的,但并不存在于实际的 js 代码中。
ECMAScript中对Reference的介绍:
A Reference is a resolved name binding. A Reference consists of three components, the base value, the referenced name and the Boolean valued strict reference flag. The base value is either undefined, an Object, a Boolean, a String, a Number, or an environment record (10.2.1). A base value of undefined indicates that the reference could not be resolved to a binding. The referenced name is a String.
这段讲述了Reference的构成,由三个部分构成,分别是:
- base value
- referenced name
- strict reference
- base value就是属性所在的对象或则就是EnviromentRecord,它的值只可能是undefiend、an Object、a Boolean、a String、a Number、or enviroment record中的一种。
- referenced name就是属性的名称
举个例子:
var foo = 1;
// 对应的Reference就是:
var fooReference = {
base: EnviromentRecord,
name: 'foo',
strict: false
};
再举一个例子:
var foo = {
bar: function (){
return this;
}
}
foo.bar(); // foo
// bar对应的Reference就是:
var BarReference = {
base: foo,
propertyName: 'bar',
strict: false
};
规范中提供了获取 Reference 组成部分的方法,比如 GetBase 和 IsPropertyReference。
GetBase
GetBase(V). Returns the base value component of the reference V.
返回 reference 的 base value。
IspropertyReference
IsPropertyReference(V). Returns true if either the base value is an object or HasPrimitiveBase(V) is true; otherwise returns false.
简单理解这句话就是,如果 base value 是一个对象就返回 true,否则就返回 false
GetValue
在8.7.1章规范中就讲了一个用于从 Reference 类型中获取对应值的方法:GetValue。
简单模拟一下GetValue
var foo = 1;
var fooReference = {
base: Enviroment,
name: 'foo',
strict: false
};
GetValue(fooReference) // 1
GetValue就是返回对象属性真正的值,需要注意的是:
调用 GetValue,返回的是具体的值,而不在是一个Reference
如何确定this的值
看规范11.2.3 Function Calls:
这里讲了当函数调用的时候如何确定this的取值。
只看第一步、第六步、第七步:
1.Let ref be the result of evaluating MemberExpression.
6.If Type(ref) is Reference, then
a.If IsPropertyReference(ref) is true, then
i.Let thisValue be GetBase(ref).
b.Else, the base of ref is an Environment Record
i.Let thisValue be the result of calling the ImplicitThisValue concrete method of GetBase(ref).
7.Else, Type(ref) is not Reference.
a. Let thisValue be undefined.
简单描述一下就是:
- 计算MemberExpression 的结果赋值给 ref
- 判断 ref 是不是一个 Reference 类型
- 如果 ref 是 Reference,并且 IsPropertyReference(ref) 是 true, 那么 this 的值为 GetBase(ref)
- 如果 ref 是 Reference,并且 base value 值是 Environment Record, 那么this的值为 ImplicitThisValue(ref)
- 如果 ref 不是 Reference,那么 this 的值为 undefined
具体分析
1、计算 MemberExpression 的结果赋值给 ref
什么是 MemberExpression?看规范 11.2 Left-Hand-Side Expressions:
MemberExpression:
- PrimaryExpression // 原始表达式 可以参见《JavaScript权威指南第四章》
- FunctionExpression // 函数定义表达式
- MemberExpression [ Expression ] // 属性访问表达式
- MemberExpression . IdentifierName // 属性访问表达式
- new MemberExpression Arguments // 对象创建表达式
举个例子:
function foo(){
console.log(this)
}
foo(); // MemberExpression 是 foo
function foo1(){
return function(){
console.log(this);
}
}
foo1()(); // MemberExpression 是 foo();
var foo2 = {
bar: function(){
return this;
}
}
foo.bar(); // MemberExpression 是 foo.bar
所以简单的理解就是 MenberExpression 其实就是()左边的部分。
2、判断 ref 是不是一个 Reference 类型。
关键就在于看规范中是如何处理各种 MemberExpression的,返回的结果是不是一个 Reference 类型;
举例:
var value = 1;
var foo = {
value: 2,
bar: function(){
return this.value
}
}
// 示例1
console.log(foo.bar());
// 示例2
console.log((foo.bar)());
// 示例3
console.log((foo.bar = foo.bar)());
// 示例4
console.log((false || foo.bar)());
// 示例5
console.log((foo.bar, foo.bar)());
foo.bar()
在示例1中,MemberExpression 的计算结果是 foo.bar,那么主要就是看 foo.bar 是不是一个 Reference
查看规范 11.2.1 Property Accessors,这里展示了一个计算的过程,什么都不管了,就看最后一步:
Return a value of type Reference whose base value is baseValue and whose referenced name is propertyNameString, and whose strict mode flag is strict.
这里讲到返回值就是一个 Reference 类型
根据前面讲过的内容,我们可以知道该值为:
var Reference = {
base: foo,
name: 'bar',
strict: false
}
接下来按照2.1的判断流程走:
2.1 如果 ref 是 Reference,并且 IsPropertyReference(ref) 是 true, 那么 this 的值为 GetBase(ref)
如果该值是 Reference 类型,那么 IspropertyReference(ref)的结果是多少呢?
前面已经讲过 IspropertyReference 方法,如果 base value 是一个对象,就返回true,
这里ref 的 base value 为 foo,是一个对象,所以 IspropertyReference(ref) 结果为 true。
这个时候就可以确定 this 的值了:
this = GetBase(ref)
GetBase 前面也讲过,获得 base value 的值,这个例子中就是 foo,所以this的指向就是 foo,示例1的结果就是 2
(foo.bar)()
看示例2:
console.log((foo.bar)());
foo.bar 被 () 包住的,查看规范 11.1.6 The Grouping Operator,直接看结果部分
Return the result of evaluating Expression. This may be of type Reference. NOTE This algorithm does not apply GetValue to the result of evaluating Expression.
实际上()并没有对 MemberExpression 进行计算,所以示例2的结果跟示例1是一样的
(foo.bar = foo.bar)()
在示例3中,有赋值操作符,查看规范 11.13.1 Simple Assignment ( = )计算的第三步
3.Let rval be GetValue(rref).
因为使用了 GetValue ,其返回的值不是一个 Reference 类型,而是一个具体的值,前面讲过的
按照之前的判断逻辑(2.3)
2.3 如果 ref 不是Reference,那么 this 的值为 undefined
this 为 undefined ,非严格模式下,this 的值为 undefined 的时候,其值会被隐式的转换为全局对象。
(false || foo.bar)()
示例4中,逻辑与算法,查看规范 11.11 Binary Logical Operators:
计算第二步:
2.Let lval be GetValue(lref).
因为使用的 GetValue ,所以返回的结果不是 Reference 类型,this 为 undefined
(foo.bar, foo.bar)()
示例5,逗号操作符,查看规范11.14 Comma Operator ( , )计算第二步:
2.Call GetValue(lref).
因为使用的 GetValue ,所以返回的结果不是 Reference 类型,this 为 undefined
补充
一个最最普通的情况:
function foo(){
console.log(this)
}
foo();
MemberExpression 是 foo,解析标识符,查看规范10.3.1 Identifier Resolution,会返回一个 Reference 类型的值:
var fooReference = {
base: EnvironmentRecord,
name: 'foo',
strict: false
};
接下来判断:
2.1 如果 ref 是 Reference,并且 IsPropertyReference(ref) 是 true, 那么 this 的值为 GetBase(ref)
因为 base value 是 EnvironmentRecord,并不是一个 Object 类型,还记得前面讲过的 base value 的取值可能吗? 只可能是 undefined, an Object, a Boolean, a String, a Number, 和 an environment record 中的一种。
IsPropertyReference(ref) 的结果为 false,进入下个判断:
2.2 如果 ref 是 Reference,并且 base value 值是 Environment Record, 那么this的值为 ImplicitThisValue(ref)
base value 正是 Environment Record,所以会调用 ImplicitThisValue(ref)
查看规范 10.2.1.1.6,ImplicitThisValue 方法的介绍:该函数始终返回 undefined。
所以最后 this 的值就是 undefined。