深入 JavaScript 的 this
欢迎关注微信公众号:前端阅读室
前言
在《深入 JavaScript 执行上下文栈》中我们讲到,当 JavaScript 执行一段可执行代码(executable code)时,会创建对应的执行上下文(execution context)。
对于每个执行上下文,都有三个重要属性:
- 变量对象(Variable object,VO)
- 作用域链(Scope chain)
- this
今天我们来讲解下 this
关于 this 的介绍可以参考如下规范:
ECMAScript 的类型分为语言类型和规范类型。
语言类型是开发者直接使用 ECMAScript 可以操作的,其实就是我们常说的 Undefined, Null, Boolean, String, Number 和 Object 等。
而规范类型相当于 meta-values,是用来用算法描述 ECMAScript 语言结构和 ECMAScript 语言类型的。
Reference 类型就是一种规范类型。它与 this 有着密切的关联。
Reference
Reference 类型是用来解释诸如 delete、typeof 以及赋值等操作行为的。
Reference 由三个部分组成,分别是:
- base value
- referenced name
- strict reference
base value 就是属性所在的对象或者就是 EnvironmentRecord,它的值只可能是 undefined、Object、Boolean、String、Number、environment record 其中的一种。
referenced name 就是属性的名称。
举个例子:
var foo = 1;
// 对应的Reference是:
var fooReference = {
base: EnvironmentRecord,
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 方法返回 reference 的 base value。
IsPropertyReference 方法简单的理解就是:如果 base value 是一个对象,就返回 true。
GetValue
GetValue 是用于从 Reference 类型获取对应值的方法。
简单模拟 GetValue 的使用如下:
var foo = 1;
var fooReference = {
base: EnvironmentRecord,
name: "foo",
strict: false
};
GetValue(fooReference); // 1;
GetValue 会返回对象属性真正的值,但是要注意:
调用 GetValue,返回的将是具体的值,而不再是一个 Reference。
如何确定 this 的值
由于 Reference 和 this 息息相关,所以我们才花了这么大的篇幅讲解。知道了 Reference,接下来我们就可以确定 this 的取值规则了。
当函数调用的时候,this 的取值规则如下:
-
计算 MemberExpression 的结果赋值给 ref
-
判断 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.计算 MemberExpression 的结果赋值给 ref
MemberExpression :
- PrimaryExpression // 原始表达式
- 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
所以可以简单理解 MemberExpression 其实就是()左边的部分。
2.判断 ref 是不是一个 Reference 类型。
关键就在于看规范是如何处理各种 MemberExpression,返回的结果是不是一个 Reference 类型。
this 取值规则举例
我们最后来看个例子,看看例子中各个 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)());
foo.bar()
规范 11.2.1 说明 Property Accessors 是一个 Reference。
根据之前的内容,我们知道该值如下:
var Reference = {
base: foo,
name: "bar",
strict: false
};
接下来按照 2.1 的判断流程走:
2.1 如果 ref 是 Reference,并且 IsPropertyReference(ref) 是 true,
那么 this 的值为 GetBase(ref)
前面我们已经说过 IsPropertyReference 方法,如果 base value 是一个对象,结果返回 true。
这个时候我们就可以确定 this 的值为 GetBase(ref)。
GetBase 方法返回 reference 的 base value,所以 this 的值就是 foo ,示例 1 打印的结果是 2。
(foo.bar)()
foo.bar 被 () 包住,查看规范 11.1.6 The Grouping Operator
实际上 () 并没有对 MemberExpression 进行计算,所以返回的结果和示例 1 一样。
(foo.bar = foo.bar)()
有赋值操作符,查看规范 11.13.1 Simple Assignment ( = ):
Let rval be GetValue(rref).
因为使用了 GetValue,所以返回的值不是 Reference 类型,
按照之前讲的判断逻辑:如果 ref 不是 Reference,那么 this 的值为 undefined
在非严格模式下,this 的值为 undefined 时,其值会被隐式转换为全局对象,所以打印的结果是 1.
(false || foo.bar)()
查看规范 11.11 Binary Logical Operators:
Let lval be GetValue(lref).
因为使用了 GetValue,所以返回的不是 Reference 类型,this 为 undefined,打印的结果是 1。
(foo.bar, foo.bar)()
看示例 5,逗号操作符,查看规范 11.14 Comma Operator (,)
Call GetValue(lref).
因为使用了 GetValue,所以返回的不是 Reference 类型,this 为 undefined,打印的结果是 1。
例子打印结果
所以例子的打印结果如下:
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
注意:以上是在非严格模式下的结果,严格模式下因为 this 返回 undefined,示例 3 就会报错。
补充
最后,补充一个最普通的情况:
function foo() {
console.log(this);
}
foo();
MemberExpression 是 foo,解析标识符,查看规范 10.3.1 Identifier Resolution,会返回一个 Reference 类型的值:
var fooReference = {
base: EnvironmentRecord,
name: "foo",
strict: false
};
由于 IsPropertyReference(ref) 的结果为 false,进入 2.2 判断:
2.2 如果 ref 是 Reference,并且 base value 值是 Environment Record,
那么 this 的值为 ImplicitThisValue(ref)
查看规范 10.2.1.1.6:ImplicitThisValue 方法始终返回 undefined。
所以最后 this 的值是 undefined。
总结
我们可以简单地理解成 this 为调用函数的对象,不过只有结合规范在一些情况下我们才能更好地判断 this。
比如下面这个例子,大家觉得会打印出什么值呢?
var value = 1;
var foo = {
value: 2,
bar: function() {
return this.value;
}
};
console.log((false || foo.bar)());
欢迎关注微信公众号:前端阅读室