JavaScript深切之从ECMAScript范例解读this

JavaScript深切系列第六篇,本篇我们追根溯源,从 ECMAScript5 范例解读 this 在函数挪用时究竟是怎样肯定的。

媒介

《JavaScript深切之实行上下文栈》中讲到,当JavaScript代码实行一段可实行代码(executable code)时,会建立对应的实行上下文(execution context)。

关于每一个实行上下文,都有三个主要属性

  • 变量对象(Variable object,VO)

  • 作用域链(Scope chain)

  • this

本日重点讲讲 this,然则不好讲。

……

由于我们要从 ECMASciript5 范例最先讲起。

先送上 ECMAScript 5.1 范例地点:

英文版:http://es5.github.io/#x15.1

中文版:http://yanhaijing.com/es5/#115

让我们最先相识范例吧!

Types

首先是第 8 章 Types:

Types are further subclassified into ECMAScript language types and specification types.

An ECMAScript language type corresponds to values that are directly manipulated by an ECMAScript programmer using the ECMAScript language. The ECMAScript language types are Undefined, Null, Boolean, String, Number, and Object.

A specification type corresponds to meta-values that are used within algorithms to describe the semantics of ECMAScript language constructs and ECMAScript language types. The specification types are Reference, List, Completion, Property Descriptor, Property Identifier, Lexical Environment, and Environment Record.

我们简朴的翻译一下:

ECMAScript 的范例分为言语范例和范例范例。

ECMAScript 言语范例是开发者直接运用 ECMAScript 能够操纵的。实在就是我们常说的Undefined, Null, Boolean, String, Number, 和 Object。

而范例范例相当于 meta-values,是用来用算法形貌 ECMAScript 言语构造和 ECMAScript 言语范例的。范例范例包含:Reference, List, Completion, Property Descriptor, Property Identifier, Lexical Environment, 和 Environment Record。

没懂?没紧要,我们只需晓得在 ECMAScript 范例中另有一种只存在于范例中的范例,它们的作用是用来形貌言语底层行动逻辑。

本日我们要讲的重点是就是个中的 Reference 范例。它与 this 的指向有着亲昵的关联。

Reference

那什么又是 Reference ?

让我们看 8.7 章 The Reference Specification Type:

The Reference type is used to explain the behaviour of such operators as delete, typeof, and the assignment operators.

所以 Reference 范例就是用来诠释诸如 delete、typeof 以及赋值等操纵行动的。

剽窃尤雨溪大大的话,就是:

这里的 Reference 是一个 Specification Type,也就是 “只存在于范例里的笼统范例”。它们是为了更好地形貌言语的底层行动逻辑才存在的,但并不存在于现实的 js 代码中。

再看接下来的这段详细引见 Reference 的内容:

A Reference is a resolved name binding.

A Reference consists of three components, the base value, the referenced name and the Boolean valued strict reference flag.

The base value is either undefined, an Object, a Boolean, a String, a Number, or an environment record (10.2.1).

A base value of undefined indicates that the reference could not be resolved to a binding. The referenced name is a String.

这段报告了 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。

这两个要领很简朴,简朴看一看:

1.GetBase

GetBase(V). Returns the base value component of the reference V.

返回 reference 的 base value。

2.IsPropertyReference

IsPropertyReference(V). Returns true if either the base value is an object or HasPrimitiveBase(V) is true; otherwise returns false.

简朴的邃晓:假如 base value 是一个对象,就返回true。

GetValue

除此之外,紧接着在 8.7.1 章范例中就讲了一个用于从 Reference 范例猎取对应值的要领: GetValue。

简朴模仿 GetValue 的运用:

var foo = 1;

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

GetValue(fooReference) // 1;

GetValue 返回对象属性真正的值,然则要注重:

挪用 GetValue,返回的将是详细的值,而不再是一个 Reference

这个很主要,这个很主要,这个很主要。

怎样肯定this的值

关于 Reference 讲了那末多,为何要讲 Reference 呢?究竟 Reference 跟本文的主题 this 有哪些关联呢?假如你能耐烦看完之前的内容,以下最先进入高能阶段:

看范例 11.2.3 Function Calls:

这里讲了当函数挪用的时刻,怎样肯定 this 的取值。

只看第一步、第六步、第七步:

1.Let ref be the result of evaluating MemberExpression.

6.If Type(ref) is Reference, then

  a.If IsPropertyReference(ref) is true, then

      i.Let thisValue be GetBase(ref).

  b.Else, the base of ref is an Environment Record

      i.Let thisValue be the result of calling the ImplicitThisValue concrete method of GetBase(ref).

7.Else, Type(ref) is not Reference.

  a. Let thisValue be undefined.

让我们形貌一下:

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?看范例 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 实在就是()左侧的部份。

2.推断 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()

在示例 1 中,MemberExpression 盘算的效果是 foo.bar,那末 foo.bar 是否是一个 Reference 呢?

检察范例 11.2.1 Property Accessors,这里展现了一个盘算的历程,什么都不管了,就看末了一步:

Return a value of type Reference whose base value is baseValue and whose referenced name is propertyNameString, and whose strict mode flag is strict.

我们得知该表达式返回了一个 Reference 范例!

依据之前的内容,我们晓得该值为:

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

接下来根据 2.1 的推断流程走:

2.1 假如 ref 是 Reference,而且 IsPropertyReference(ref) 是 true, 那末 this 的值为 GetBase(ref)

该值是 Reference 范例,那末 IsPropertyReference(ref) 的效果是多少呢?

前面我们已铺垫了 IsPropertyReference 要领,假如 base value 是一个对象,效果返回 true。

base value 为 foo,是一个对象,所以 IsPropertyReference(ref) 效果为 true。

这个时刻我们就可以够肯定 this 的值了:

this = GetBase(ref),

GetBase 也已铺垫了,取得 base value 值,这个例子中就是foo,所以 this 的值就是 foo ,示例1的效果就是 2!

唉呀妈呀,为了证实 this 指向foo,真是累死我了!然则晓得了道理,剩下的就更快了。

(foo.bar)()

看示例2:

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

foo.bar 被 () 包住,检察范例 11.1.6 The Grouping Operator

直接看效果部份:

Return the result of evaluating Expression. This may be of type Reference.

NOTE This algorithm does not apply GetValue to the result of evaluating Expression.

现实上 () 并没有对 MemberExpression 举行盘算,所以实在跟示例 1 的效果是一样的。

(foo.bar = foo.bar)()

看示例3,有赋值操纵符,检察范例 11.13.1 Simple Assignment ( = ):

盘算的第三步:

3.Let rval be GetValue(rref).

由于运用了 GetValue,所以返回的值不是 Reference 范例,

根据之前讲的推断逻辑:

2.3 假如 ref 不是Reference,那末 this 的值为 undefined

this 为 undefined,非严厉形式下,this 的值为 undefined 的时刻,其值会被隐式转换为全局对象。

(false || foo.bar)()

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

盘算第二步:

2.Let lval be GetValue(lref).

由于运用了 GetValue,所以返回的不是 Reference 范例,this 为 undefined

(foo.bar, foo.bar)()

看示例5,逗号操纵符,检察范例11.14 Comma Operator ( , )

盘算第二步:

2.Call GetValue(lref).

由于运用了 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,剖析标识符,检察范例 10.3.1 Identifier Resolution,会返回一个 Reference 范例的值:

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

接下来举行推断:

2.1 假如 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,进入下个推断:

2.2 假如 ref 是 Reference,而且 base value 值是 Environment Record, 那末this的值为 ImplicitThisValue(ref)

base value 恰是 Environment Record,所以会挪用 ImplicitThisValue(ref)

检察范例 10.2.1.1.6,ImplicitThisValue 要领的引见:该函数一直返回 undefined。

所以末了 this 的值就是 undefined。

多说一句

只管我们能够简朴的邃晓 this 为挪用函数的对象,假如是如许的话,怎样诠释下面这个例子呢?

var value = 1;

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

另外,又怎样肯定挪用函数的对象是谁呢?在写文章之初,我就面临着这些题目,末了照样摒弃从多个情况下给人人解说 this 指向的思绪,而是追根溯源的从 ECMASciript 范例解说 this 的指向,只管从这个角度写起来和读起来都比较费劲,然则一旦多读几遍,邃晓道理,相对会给你一个全新的视角对待 this 。而你也就可以邃晓,只管 foo() 和 (foo.bar = foo.bar)() 末了效果都指向了 undefined,然则二者从范例的角度上却有着实质的区分。

此篇解说实行上下文的 this,即使不是很邃晓此篇的内容,依旧不影响人人相识实行上下文这个主题下其他的内容。所以,依旧能够放心的看下一篇文章。

下一篇文章

《JavaScript深切之实行上下文》

深切系列

JavaScript深切系列目次地点:https://github.com/mqyqingfeng/Blog

JavaScript深切系列估计写十五篇摆布,旨在帮人人捋顺JavaScript底层学问,重点解说如原型、作用域、实行上下文、变量对象、this、闭包、按值通报、call、apply、bind、new、继续等难点观点。

假如有毛病或许不严谨的处所,请务必赋予斧正,非常谢谢。假如喜好或许有所启示,迎接star,对作者也是一种勉励。

    原文作者:冴羽
    原文地址: https://segmentfault.com/a/1190000009048715
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞