前端基础 | 结合规范了解this

通过阅读大佬文章,结合es规范,重新学习下 this的问题

建议将上面文章结合规范一起阅读

接上一篇执行上下文

执行上下文分为两个阶段:

  1. 创建执行上下文
  2. 执行阶段

创建执行上下文又分为 3 步:

  1. 确定 this 指向
  2. LexicalEnvironment(词法环境) 组件被创建
  3. VariableEnvironment(变量环境) 组件被创建

这里只说说 this

ECMAScript 的类型分为语言类型规范类型

我们常说的 Undefined, Null, Boolean, String, NumberObject 属于 语言类型

规范类型「只存在于规范里的抽象类型」。它们是为了更好地描述语言的底层行为逻辑才存在的,但并不存在于实际的 js 代码中。

规范类型包括:Reference, List, Completion, Property Descriptor, Property Identifier, Lexical Environment, 和 Environment Record

其中 Reference 类型 和 this 的指向有密切联系。

Reference 有且仅有三个组成部分:

  • base value 属性所在的对象或者就是 EnvironmentRecord,值只可能是 undefined, an Object, a Boolean, a String, a Number, or an environment record中的一种
  • referenced name 属性的名称。
  • strict reference 是否是严格模式

通过两个例子来记忆一下:

1
2
3
4
5
6
7
8
var foo = 1;

// 对应的Reference是:
var fooReference = {
base: EnvironmentRecord,
name: 'foo',
strict: false
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var foo = {
bar: function () {
return this;
}
};

foo.bar(); // foo

// bar对应的Reference是:
var BarReference = {
base: foo,
propertyName: 'bar',
strict: false
};

Reference 提供了两个方法:

  • GetBase: 返回 referencebase value。**返回的是一个具体值,而不再是一个 Reference**。
  • IsPropertyReference: base value 是一个对象,就返回true,否则返回 false

这里再引入一个新名词:MemberExpression

查阅ES规范:http://es5.github.io/#x11.2

MemberExpression 的值为:

  • PrimaryExpression // 原始表达式
  • FunctionExpression // 函数定义表达式
  • MemberExpression [ Expression ] // 属性访问表达式
  • MemberExpression . IdentifierName // 属性访问表达式
  • new MemberExpression Arguments // 对象创建表达式

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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 其实就是()左边的部分。

如何确定 this?

建议仔细理解前面的内容!

查阅 ES规范http://es5.github.io/#x11.2.3,简单总结成2步:

  1. 计算 MemberExpression 的结果赋值给 ref
  2. 判断 ref 是不是一个 Reference 类型
    • 2.1 如果 ref 是 Reference,并且 IsPropertyReference(ref) 是 true, 那么 this 的值为 GetBase(ref)(由前面可知,GetBase(ref)即为 base value)
    • 2.2 如果 ref 是 Reference,并且 base value 值是 Environment Record, 那么this的值为 ImplicitThisValue(ref)查阅 es规范:http://es5.github.io/#x10.2.1.2.6 可知,ImplicitThisValue(ref) 函数始终返回 undefined
    • 2.3 如果 ref 不是 Reference,那么 this 的值为 undefined

如何判断是不是 Reference 类型

建议在「英文版规范」中全局搜索关键词 Return a value of type Referen,然后阅读搜索结果所属部分的内容. 得出如下结论:

两种创建 Reference 的途径:

  • 标识符解析
  • 属性访问

比如,foofoo.bar 创建了一个 Reference ,而字面量(1,“foo”,[1,3]等)或函数表达式——(function(){})却不会。有一张来自网络的图作了个概括:

reference.png

例子:

1
2
3
4
5
6
7
8
9
10
11
var value = 1;

var foo = {
value: 2,
bar: function () {
return this.value;
}
}

//示例1
console.log(foo.bar());

根据上述的两个步骤进行判断:

foo.bar()

  1. MemberExpression 的结果为 foo.bar
  2. 确定 foo.bar 是不是 Reference 类型(前面介绍了如何判断,不清楚的往前翻一翻):
    • 2.1 foo.bar 属于属性访问,所以它是 Reference 类型

由前文得出:

1
2
3
4
5
var Reference = {
base: foo,
name: 'bar',
strict: false
};

由前文,判断出 IsPropertyReference(ref) 值为 true,所以 this 值为 GetBase(ref),即为 base value,所以 this 值为 foo

例子:

1
2
3
4
5
function foo() {
console.log(this)
}

foo();

分析:

1
2
3
4
5
var reference = {
base: EnvironmentRecord,
name: 'foo',
strict: false
}

EnvironmentRecord 不属于 Object,所以 IsPropertyReference(ref)为 false ,则 this 指向 ImplicitThisValue(ref),而前面说了,

所以 this 指向 undefined

注意:

如果你在浏览器执行上述代码,你会在控制台发现输出的是 window对象??

查阅规范http://es5.github.io/#x10.4.3
Entering Function Code」,第二条显示。

if thisArg is null or undefined, set the ThisBinding to the global object.

参考文章:https://github.com/mqyqingfeng/Blog/issues/7