# 了解一下 this
# 提出疑问
先来看一段程序
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)())
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
如果你一次就能全部答对,恭喜你,不用看文章了
# 前置知识
# Reference 类型
ECMAScript 的类型分为语言类型和规范类型。
ECMAScript 语言类型是开发者直接使用 ECMAScript 可以操作的。其实就是我们常说的 Undefined, Null, Boolean, String, Number, 和 Object。
而规范类型相当于 meta-values,是用来用算法描述 ECMAScript 语言结构和 ECMAScript 语言类型的。规范类型包括:Reference, List, Completion, Property Descriptor, Property Identifier, Lexical Environment, 和 Environment Record。
# 什么是 Reference?
Reference 类型就是用来解释诸如 delete、typeof 以及赋值等操作行为的。
Reference 的构成,有三个组成部分,分别是:
- base value
- referenced name
- strict reference
可是这些到底是什么呢?
我们简单的理解的话:
base value 就是属性所在的对象或者就是 EnvironmentRecord,它的值只可能是 undefined, an Object, a Boolean, a String, a Number, or an environment record 其中的一种。
referenced name 就是属性的名称。
举个例子:
var foo = 1
// 对应的Reference是:
var fooReference = {
base: EnvironmentRecord,
name: 'foo',
strict: false
}
2
3
4
5
6
7
8
再举个例子:
var foo = {
bar: function() {
return this
}
}
foo.bar() // foo
// bar对应的Reference是:
var BarReference = {
base: foo,
propertyName: 'bar',
strict: false
}
2
3
4
5
6
7
8
9
10
11
12
13
14
而且规范中还提供了获取 Reference 组成部分的方法,比如 GetBase 和 IsPropertyReference。
这两个方法很简单,简单看一看:
1.GetBase
GetBase(V). Returns the base value component of the reference V.
返回 reference 的 base value。
2.IsPropertyReference
IsPropertyReference(V). Returns true if either the base value is an object or HasPrimitiveBase(V) is true; otherwise returns false.
简单的理解:如果 base value 是一个对象,就返回 true。
# 重点了解一下 GetValue!!!
在 8.7.1 章规范中就讲了一个用于从 Reference 类型获取对应值的方法: GetValue。
简单模拟 GetValue 的使用:
var foo = 1
var fooReference = {
base: EnvironmentRecord,
name: 'foo',
strict: false
}
GetValue(fooReference) // 1;
2
3
4
5
6
7
8
9
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
1
i.Let thisValue be GetBase(ref).
1
b.Else, the base of ref is an Environment Record
1
i.Let thisValue be the result of calling the ImplicitThisValue concrete method of GetBase(ref).
17.Else, Type(ref) is not Reference.
a. Let thisValue be undefined.
1
让我们描述一下:
1.计算 MemberExpression 的结果赋值给 ref
2.判断 ref 是不是一个 Reference 类型
2.1 如果 ref 是 Reference,并且 IsPropertyReference(ref) 是 true, 那么 this 的值为 GetBase(ref)
2.2 如果 ref 是 Reference,并且 base value 值是 Environment Record, 那么 this 的值为 ImplicitThisValue(ref)
2.3 如果 ref 不是 Reference,那么 this 的值为 undefined
# 具体分析
让我们一步一步看:
- 计算 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 foo() {
return function() {
console.log(this)
}
}
foo()() // MemberExpression 是 foo()
var foo = {
bar: function() {
return this
}
}
foo.bar() // MemberExpression 是 foo.bar
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
所以简单理解 MemberExpression 其实就是()左边的部分。
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)())
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 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
3
4
5
接下来按照 2.1 的判断流程走:
2.1 如果 ref 是 Reference,并且 IsPropertyReference(ref) 是 true, 那么 this 的值为 GetBase(ref)
该值是 Reference 类型,那么 IsPropertyReference(ref) 的结果是多少呢?
前面我们已经铺垫了 IsPropertyReference 方法,如果 base value 是一个对象,结果返回 true。
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 进行计算,所以其实跟示例 1 的结果是一样的。
# (foo.bar = foo.bar)()
看示例 3,有赋值操作符,查看规范 11.13.1 Simple Assignment ( = ):
计算的第三步:
3.Let rval be GetValue(rref).
因为使用了 GetValue,所以返回的值不是 Reference 类型,
按照之前讲的判断逻辑:
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
# 程序答案
var value = 1
var foo = {
value: 2,
bar: function() {
return this.value
}
}
//示例1
console.log(foo.bar()) // 2
//示例2
console.log(foo.bar()) // 2
//示例3
console.log((foo.bar = foo.bar)()) // 1
//示例4
console.log((false || foo.bar)()) // 1
//示例5
console.log((foo.bar, foo.bar)()) // 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 参考
ECMAScript 5.1 规范地址:
英文版:http://es5.github.io/#x15.1
中文版:http://yanhaijing.com/es5/#115
← Promise基本实现 虚拟列表 →