前言
一直以来对this
的粗浅理解:如果一个函数被一个对象所拥有,在调用的时候以[对象.函数]的形式调用,那么函数内部的this
则指向这个对象;如果是直接调用函数内部的this
默认指向undefined
,非严格模式下,会被自动指向全局对象。
就如同下面的例子一般,前者输出2,后者输出1:
1 | var value = 1 |
然而看到下一个例子我就懵逼了:
1 | var value = 1; |
从规范层面确定this
的值
Types和Reference
ECMAScript的类型分为语言类型和规范类型
语言类型就是我们平时常说的基本类型(Null Undefined Boolean String Number Symbol)和复杂类型(Object);而规范类型相当于 meta-values,是用算法来描述 ECMAScript 语言结构和 ECMAScript 语言类型的。规范类型包括:Reference, List, Completion, Property Descriptor, Property Identifier, Lexical Environment, 和 Environment Record。
我们只要知道在 ECMAScript 规范中还有一种只存在于规范中的类型,它们的作用是用来描述语言底层行为逻辑。
这里只需要简单了解 Reference 类型
Reference 类型就是用来解释诸如 delete、typeof 以及赋值等操作行为的
Reference的组成由三个部分组成,分别是:
- base value
- reference name 属性的名称
- staict reference
base value 就是属性所在的对象或者就是 EnvironmentRecord,它的值只可能是 undefined, an Object, a Boolean, a String, a Number, or an environment record 其中的一种。
举个例子:
1 | var foo = 1; |
在举个例子:
1 | var foo = { |
需要了解的Reference组成部分的两个方法:
GetBase
返回Reference的base valueIsPropertyReference
如果base value是一个对象返回true,否则返回false
从Reference类型获取对应值的方法GetValue
,GetValue返回的是具体的值,不再是一个Reference
注意GetBase
IsPropertyRerence
GetValue
都是底层的规范,在浏览器上是无法直接使用的。
确定this
的步骤
计算
MemberExpression
的结果赋值给ref
简单理解:MemberExpression 其实就是()
左边的部分。
关键就在于看规范是如何处理各种 MemberExpression
举个例子:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16function 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判断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
举个例子:
1 | var value = 1; |
foo.bar()
根据规则返回的Reference为1
2
3
4
5var Reference = {
base: foo,
name: bar,
strict: false
}根据规则首先ref是一个reference,IsPropertyReference(ref)是true,所以this的值为GetBase(ref),即foo
(foo.bar)()
()
没有对MemberExpression进行计算,所以结果和示例1一样(foo.bar = foo.bar)()
规范:2.Let lval be GetValue(lref).
有赋值操作符,会使用GetValue方法,然后返回的就不是一个reference,this为undefined,非严格模式下this为undefined会被隐式的转换成window
(false || foo.bar)()
规范:3.Let rval be GetValue(rref).
逻辑与算法,使用GetValue方法,之后和示例3一样
(foo.bar, foo.bar)()
规范:2.Call GetValue(lref).
逗号运算符,使用GetValue方法,之后和示例3一样
foo()
这是很常见的场景,this也是指向的window,但是本质却和示例3、4、5不一样
根据规则返回的Reference为1
2
3
4
5var Reference = {
base: EnvironmentRecord,
name: bar,
strict: false
}ref是reference,但 IsPropertyReference(ref) 是 false,那么this的值为 ImplicitThisValue(ref),ImplicitThisValue 函数始终返回 undefined。所以在非严格模式下,this指向window。
ps:箭头函数的this是父级(词法上的)作用域的this
小结
其实之前我们简单粗暴的方式能够应付大多数情况下this的判断,但是不严谨,无法判断类似(false || foo.bar)()
的情况,而且有时即使判断this的值是正确的,但是背后的逻辑却是不同的。所以需要从规范层面来理解this确定的过程。