换个角度理解this

260 阅读3分钟

在看到JavaScript深入之从ECMAScript规范解读this这篇文章之前,对this的理解仅仅是调用函数的对象。看完这篇文章之后对this的理解有了进一步的认识。

ES规范

在ES标准中,ES的类型分为语言类型和规范类型;其中语言类型就是我们在代码中可操作的类型,例如Undefined、Number、String、Boolean、Object等,规范类型是用算法来描述ES的语言结构和语言类型,包括Referenc, eList, Completion, Property Descriptor, Property Identifier, Lexical Environment, 和 Environment Record;看到这些单词时内心第一反应是:“这些是个啥?”,好在JavaScript深入之从ECMAScript规范解读this里说没懂也没有关系,只需知道ES规范中还存在一种只存在于规范里的类型,它的作用是用来描述语言底层行为逻辑。

Reference

在规范类型中我们发现了有一种叫Reference的类型,那什么是Reference呢?Reference是用来解释诸如delete、typeof和赋值等操作行为。Reference有如下三部分组成:

  • base value // 属性所在的对象或者就是EnvironmentRecord,它的值只可能是undefined、string、number、boolean、object或者environmentRecord
  • name // 属性的名字
  • strict // 是否采用了严格模式

例如如下代码:

var foo = 1;
// 对应的Reference
var fooReference = {
    baseValue: EnvironmentRecord,
    name: 'foo',
    strict: false
}
var foo = {
    bar: function () {
        return this
    }
}
// foo.bar()中bar对应的Reference
var barReference = {
    baseValue: foo,
    name: 'bar',
    strict: false
}

在ES规范中还提供了获取对应属性值的方法:

  • GetBase(reference):获取Reference类型的base属性值
  • IsPropertyReference(reference):判断base属性值是否为object,是的话返回true否则返回false
  • GetValue(reference):获取对应变量的值,其返回值类型一定不是Reference

this的确定

说了这么多,终于进入主题了?在此之前为什么介绍Reference,主要是由于Reference和this的确定有着很大的关联;下面就来介绍ES规范中是怎么确定this的:

1. 计算MemberExpression值,赋给ref
2. 判断ref是否是Reference类型,如果是的话走第3步,不是的话就走第4步
3. 判断IsPropertyReference返回值是否为true,即baseValue是否为Object;如果是的话this就是baseValue,否则this就为undefined在非严格模式下会被转成window
4. 不是Reference的话,this的值就为undefined,在非严格模式下会被转成window

上面的第一步我们提到了“计算MemberExpression值,赋给ref”,那“计算MemberExpression值”到底是什么呢?在确定函数this的时候我们只需记住下面的话就行:

最后一个()前面的内容

例子:

function foo(){}
foo() // 对应的MemberExpression为foo
var obj = {
    bar: function(){}
}
obj.bar() // 对应的MemberExpression为obj.bar

到此this的确定就基本介绍完了,下面我们看几个例子:

var val = 'outer';

var obj = {
  val: 'inner',
  bar: function () {
    return this.value;
  }
}

//示例1
console.log(obj.bar());
//示例2
console.log((obj.bar = obj.bar)());
//示例3
console.log((false || obj.bar)());
//示例4
console.log((obj.bar, obj.bar)());

示例1. obj.bar()

1.确定MemberExpression:obj.bar
2.判断是否为Reference类型:是,对应的属性值如下:

reference  = {
    base: obj,
    name: 'bar',
    strict: false
}
  1. 是Reference类型的情况下,判断IsPropertyReference的返回值,由于obj是对象所以返回true,那么this就是等于base属性值为obj

示例2、示例3和示例4

由于MemberExpression的值都是运算表达式,会通过GetValue来获取运算结果,上面提到过GetValue返回的值都不是Reference类型,所以ref不是Reference类型,这样的话this就是undefined,非严格模式下会被转换成window