JavaScript-this

前言:

当 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的值:

让我们描述一下:

  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 其实就是()左边的部分。

  1. 判断 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作为执行上下文的一个属性,可以在函数执行的过程中用到。

上一篇:JVM如何定位到访问的对象


下一篇:Java中各种引用(Reference)解析