likes
comments
collection
share

深入 JavaScript 的 this

作者站长头像
站长
· 阅读数 12

欢迎关注微信公众号:前端阅读室

前言

在《深入 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 的取值规则如下:

  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

具体分析

让我们一步一步来分析:

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)());

欢迎关注微信公众号:前端阅读室