Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

This #9

Open
L-small opened this issue Mar 29, 2020 · 0 comments
Open

This #9

L-small opened this issue Mar 29, 2020 · 0 comments

Comments

@L-small
Copy link
Owner

L-small commented Mar 29, 2020

在上篇中讲到,当 JavaScript 代码执行一段可执行代码(全局代码、函数代码、eval函数)时,会创建对应的执行上下文(execution context)。

对于每个执行上下文,都有三个重要属性:

  • 变量对象(Variable object,VO)
  • 作用域链(Scope chain)
  • this

今天重点讲讲This,会涉及到ECMAScript规范和运算符优先级。

ECMAScript 5.1 规范地址:

英文版:http://es5.github.io/#x15.1
中文版:http://yanhaijing.com/es5/#115
运算符优先级:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Operator_Precedence

Type

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

ECMAScript 语言类型是开发者直接使用 ECMAScript 可以操作的。其实就是我们常说的Undefined, Null, Boolean, String, Number, 和 Object。

规范类型它们的作用是用来描述语言底层行为逻辑。规范类型包括:Reference, List, Completion, Property Descriptor, Property Identifier, Lexical Environment, 和 Environment Record。

Reference

Reference是一个规范类型(实际并不存在),也就是说是为了解释规范某些行为而存在的,比如delete、typeof、赋值语句等。规范类型设计用于解析命名绑定的,它由三部分组成:

  • base value
  • referenced name
  • strict reference flag 标示是否严格模式

其实就是规范中定义了一种类型叫做Reference用来引用其他变量,它有一个规定的数据结构。由于是规范类型,所以什么情况下会返回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。

  • GetBase 返回 reference 的 base value
  • IsPropertyReference 如果 base value 是一个对象,就返回true

GetValue

从 Reference 类型获取对应值的方法,简单模拟 GetValue 的使用:

var foo = 1;

var fooReference = {
    base: EnvironmentRecord,
    name: 'foo',
    strict: false
};

GetValue(fooReference) // 1;

GetValue 返回对象属性真正的值,但是要注意:调用 GetValue,返回的将是具体的值,而不再是一个 Reference

如何确定this的值

当函数调用的时候,如何确定 this 的取值。如下:

image

具体分析

让我们一步一步看:

  1. 计算 MemberExpression 的结果赋值给 ref
  2. 判断 ref 是不是一个 Reference 类型。
  3. 然后根据上面的逻辑推算出this。

MemberExpression

什么是 MemberExpression?看规范 11.2 Left-Hand-Side Expressions:

MemberExpression :

  • PrimaryExpression // 原始表达式 可以参见《JavaScript权威指南第四章》
  • 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类型。

例子1:

function foo() {
    console.log(this)
}

foo(); 

<1> MemberExpression 是 foo,解析标识符,查看规范 10.3.1 Identifier Resolution,会返回一个 Reference 类型的值:

var fooReference = {
    base: EnvironmentRecord,
    name: 'foo',
    strict: false
};

接下来进行判断:

<2> 如果 ref 是 Reference,并且 IsPropertyReference(ref) 是 true, 那么 this 的值为 GetBase(ref)

因为 base value 是 EnvironmentRecord,并不是一个 Object 类型,还记得前面讲过的 base value 的取值可能吗? 只可能是 undefined, an Object, a Boolean, a String, a Number, 和 an environment record 中的一种。

IsPropertyReference(ref) 的结果为 false,进入下个判断:

<3> 如果 ref 是 Reference,并且 base value 值是 Environment Record, 那么this的值为 ImplicitThisValue(ref)

base value 正是 Environment Record,所以会调用 ImplicitThisValue(ref)。查看规范 10.2.1.1.6,ImplicitThisValue 方法的介绍:该函数始终返回 undefined。

所以最后 this 的值就是 undefined。

例子2:

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

在示例 1 中,MemberExpression 计算的结果是 foo.bar,foo.bar我们都知道是一个属性访问,那么执行属性访问的时候,basevalue为.运算符左边的语句即 foo 的结果

根据之前的内容,我们知道该值为:

var Reference = {
  base: foo,
  name: 'bar',
  strict: false
};

1、那么ref则是Reference。
2、由于base是foo,foo是一个Object,所以 IsPropertyReference(ref) 是 true。
3、this = GetBase(ref),GetBase(ref)就是foo。
所以this的值就是foo,所以结果是2

(foo.bar)()

看示例2:

console.log((foo.bar)());

foo.bar 被 () 包住,查看规范 11.1.6 The Grouping Operator

直接看结果部分,实际上 () 并没有对 MemberExpression 进行计算,所以其实跟示例 1 的结果是一样的。

(foo.bar = foo.bar)()

看示例3,有赋值操作符,查看规范 11.13.1 Simple Assignment ( = ):

1、rval = GetValue(rref),其实就是取右边的值,由于GetValue不会返回reference类型。
2、如果 ref 不是Reference,那么 this 的值为 undefined。

this 为 undefined,非严格模式下,this 的值为 undefined 的时候,其值会被隐式转换为全局对象。

(false || foo.bar)()

看示例4,逻辑与算法,查看规范 11.11 Binary Logical Operators:

1、lval = GetValue(lref) 同上一个,因为使用了 GetValue,所以返回的不是 Reference 类型,
2、不是reference类型,this 为 undefined

(foo.bar, foo.bar)()

看示例5,逗号操作符,查看规范11.14 Comma Operator ( , )

1、GetValue(lref) 同上,因为使用了 GetValue,所以返回的不是 Reference 类型,
2、不是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 会报错。

多说一句

尽管我们可以简单的理解 this 为调用函数的对象,如果是这样的话,如何解释下面这个例子呢?

var value = 1;

var foo = {
  value: 2,
  bar: function () {
    return this.value;
  }
}
console.log((false || foo.bar)()); // 1

所以从 ECMASciript 规范讲解 this 的指向。尽管 foo() 和 (foo.bar = foo.bar)() 最后结果都指向了 undefined,但是两者从规范的角度上却有着本质的区别。

引用

根治JS的this
JavaScript深入之从ECMAScript规范解读this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant