前言:
当 JavaScript 代码执行一段可执行代码(executable code)时,会创建对应的执行上下文(execution context)。
对于每个执行上下文,都有三个重要属性:
- 变量对象(Variable object,VO)
- 作用域链(Scope chain)
- this
Types:
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 是一个 Specification Type,也就是 “只存在于规范里的抽象类型”。它们是为了更好地描述语言的底层行为逻辑才存在的,但并不存在于实际的 js 代码中。
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
};
再举个例子:
var foo = {
bar: function () {
return this;
}
};
foo.bar(); // foo
// bar对应的Reference是:
var BarReference = {
base: foo,
propertyName: 'bar',
strict: false
};
规范中还提供了获取 Reference 组成部分的方法:
- GetBase
- IsPropertyReference
简单的理解:如果 base value 是一个对象,就返回true。
GetValue
简单模拟 GetValue 的使用:
var foo = 1;
var fooReference = {
base: EnvironmentRecord,
name: 'foo',
strict: false
};
GetValue(fooReference) // 1;
注意:
调用 GetValue,返回的将是具体的值,而不再是一个 Reference。
如何确定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。
分析:
- 计算 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 其实就是()左边的部分。
- 判断 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()
MemberExpression 计算的结果是 foo.bar,该表达式返回了一个 Reference 类型:
var Reference = {
base: foo,
name: 'bar',
strict: false
};
2.1 如果 ref 是 Reference,并且 IsPropertyReference(ref) 是 true, 那么 this 的值为 GetBase(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)()
console.log((foo.bar)());
实际上 () 并没有对 MemberExpression 进行计算,所以其实跟示例 1 的结果是一样的。
(foo.bar = foo.bar)()
因为使用了 GetValue,所以返回的值不是 Reference 类型:
2.3 如果 ref 不是Reference,那么 this 的值为 undefined
this 为 undefined,非严格模式下,this 的值为 undefined 的时候,其值会被隐式转换为全局对象。
(false || foo.bar)()
因为使用了 GetValue,所以返回的不是 Reference 类型,this 为 undefined
(foo.bar, foo.bar)()
因为使用了 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
注意:以上是在非严格模式下的结果,严格模式下因为 this 返回 undefined,所以示例 3 会报错。
补充:
function foo() {
console.log(this)
}
foo();
MemberExpression 是 foo,解析标识符:
var fooReference = {
base: EnvironmentRecord,
name: 'foo',
strict: false
};
2.1 如果 ref 是 Reference,并且 IsPropertyReference(ref) 是 true, 那么 this 的值为 GetBase(ref)。
因为 base value 是 EnvironmentRecord,并不是一个 Object 类型。
isPropertyReference(ref) 的结果为 false,进入下个判断:
2.2 如果 ref 是 Reference,并且 base value 值是 Environment Record, 那么this的值为 ImplicitThisValue(ref)。
base value 正是 Environment Record,所以会调用 ImplicitThisValue(ref)。
ImplicitThisValue 方法的介绍:该函数始终返回 undefined,所以最后 this 的值就是 undefined。
尽管我们可以简单的理解 this 为调用函数的对象:
var value = 1;
var foo = {
value: 2,
bar: function () {
return this.value;
}
}
console.log((false || foo.bar)()); // 1
总结:
- 绑定时间点:
是在函数运行时绑定的,而非定义时。它的上下文取决于函数调用时的各种条件,和在哪里定义的没有关系,只取决于函数的调用方式。
- 绑定过程
当函数被调用时,会创建一个执行上下文,在这个上下文里包含了函数在哪里没调用(调用栈),调用函数的方法,参数等。this作为执行上下文的一个属性,可以在函数执行的过程中用到。