深入之从,从ECMAScript规范解读this

日期:2019-11-19编辑作者:澳门金莎娱乐网站

JavaScript 深入之从 ECMAScript 规范解读 this

2017/05/17 · JavaScript · this

原文出处: 冴羽   

之前都是根据函数的使用情况来理解this,分为四种情况:

原文出处

JavaScript深入之从ECMAScript规范解读this
ECMA-262-3 in detail. Chapter 3. This
ECMAScript5.1中文版
ECMAScript5.1英文版

大家好,我是IT修真院深圳分院第06期学员,一枚正直善良的web程序员。

前言

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

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

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

今天重点讲讲this,然而不好讲。

……

因为我们要从ECMASciript5规范开始讲起。

先奉上ECMAScript 5.1规范地址:

英文版:

中文版:

让我们开始了解规范吧!

  • 1、作为对象方法调用
  • 2、作为普通函数调用
  • 3、构造器调用
  • 4、Function.prototype.call或Function.prototype.apply调用
    但是今天看到了一篇文章,是追根溯源的从 ECMASciript 规范讲解 this 的指向,读完感觉收获很大,赶紧记录下来。

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 规范中还有一种只存在于规范中的类型,它们的作用是用来描述语言底层行为逻辑

今天给大家分享一下,修真院官网JS任务中可能会使用到的知识点:

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。

没懂?没关系,我们重点看其中的Reference类型。

了解规范

英文版:http://es5.github.io/#x15.1
中文版:http://yanhaijing.com/es5/#115

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 的构成,由三个组成部分,分别是:

  1. base value: 就是属性所在的对象或者就是 EnvironmentRecord,它的值只可能是 undefined, an Object, a Boolean, a String。

  2. referenced name: 就是属性的名称。

  3. strict reference: 是否处于严格模式。

举个例子:

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

  1. IsPropertyReference

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

简单的理解:如果 base value 是一个对象,就返回true

简述Js中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是undefined, an Object, a Boolean, a String, a Number, or an environment record其中的一种

reference name是字符串。

可是这些到底是什么呢?

让我们简洁的理解base value是属性所在的对象或者就是EnvironmentRecord,referenced name就是属性的名称

嗯,举个例子:

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

1
2
3
4
5
6
7
var foo = 1;
 
var fooReference = {
  base: EnvironmentRecord,
  name: 'foo',
  strict: false
};

再举个例子:

var foo = { bar: function () { return this; } }; foo.bar(); // foo var fooBarReference = { base: foo, propertyName: 'bar', strict: false };

1
2
3
4
5
6
7
8
9
10
11
12
13
var foo = {
  bar: function () {
    return this;
  }
};
foo.bar(); // foo
 
var fooBarReference = {
  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是object,就返回true

1、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直接操作的类型。ECMAScript语言类型是Undefined,Null,Boolean,String, Number, 和Object。

规范类型相当于meta-values,用来用算法来描述ECMAScript 语言结构和语言类型的。规范类型是:Reference,List,Completion,Property Descriptor,Property Identifier, Lexical Environment, and Environment Record。

我们需要知道在 ECMAScript 规范中还有一种只存在于规范中的类型,它们的作用是用来描述语言底层行为逻辑。

GetValue


除此之外,紧接着在8.7.1 章规范中就讲了一个用于从 Reference 类型获取对应值的方法: GetValue

简单模拟 GetValue 的使用:

var foo = 1;

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

GetValue(fooReference) // 1;

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

1.背景介绍

当JavaScript代码执行一段可执行代码(executable code)时,会创建对应的执行上下文(execution context)。
对于每个执行上下文,都有三个重要属性:

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

GetValue

除此之外,紧接着规范中就讲了一个GetValue方法,在8.7.1章

简单模拟GetValue的使用:

var foo = 1; var fooReference = { base: EnvironmentRecord, name: 'foo', strict: false }; GetValue(fooReference) // 1;

1
2
3
4
5
6
7
8
9
var foo = 1;
 
var fooReference = {
  base: EnvironmentRecord,
  name: 'foo',
  strict: false
};
 
GetValue(fooReference) // 1;

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

那为什么要讲References呢?

2、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 类型是用来解释删除,typeof和赋值操作等行为的。
Reference由三部分组成:

  • base value
  • referenced name
  • strict reference
    简单理解,base value 就是属性所在的对象或者就是 EnvironmentRecord,它的值只可能是 undefined, an Object, a Boolean, a String, a Number, or an environment record 其中的一种。

base value 就是属性所在的对象或者就是 EnvironmentRecord,它的值只可能是 undefined, an Object, a Boolean, a String, a Number, or an environment record 其中的一种。
举两个栗子:

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。

如何确定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
    

2.知识剖析

如何确定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).

1
2
3
4
  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.

JavaScript

a. Let thisValue be undefined.

1
  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值是Environment Record, 那么this = ImplicitThisValue(ref),
2.3.如果ref不是Reference,那么 this = undefined

让我们一步一步看:

  1. 计算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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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其实就是()左边的部分

接下来就是判断MemberExpression的结果是不是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)());

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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)());

在试验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 };

1
2
3
4
5
var Reference = {
  base: foo,
  name: 'bar',
  strict: false
};

然后这个因为base value是一个对象,所以IsPropertyReference(ref)是true,

那么this = GetBase(ref),也就是foo, 所以this指向foo,试验1的结果就是 2

唉呀妈呀,为了证明this指向foo,累死我了!

剩下的就很快了:

看试验2,使用了()包住了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是一样的。

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

计算的第三步:

3.Let rval be GetValue(rref).

因为使用了GetValue,所以返回的不是reference类型,this为undefined

看试验4,逻辑云算法

查看规范11.11 Binary Logical Operators:

计算第二步:

2.Let lval be GetValue(lref).

因为使用了GetValue,所以返回的不是reference类型,this为undefined

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

计算第二步:

2.Call GetValue(lref).

澳门金莎娱乐网站,因为使用了GetValue,所以返回的不是reference类型,this为undefined

但是注意在非严格模式下,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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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();

1
2
3
4
5
function foo() {
    console.log(this)
}
 
foo();

MemberExpression是foo,解析标识符
查看规范10.3.1 Identifier Resolution

会返回一个 Reference类型

但是 base value是 Environment Record,所以会调用ImplicitThisValue(ref)

查看规范10.2.1.1.6

始终返回undefined

所以最后this的值是undefined

GetValue

除此之外,紧接着在 8.7.1 章规范中就讲了一个用于从 Reference 类型获取对应值的方法: GetValue。
简单模拟 GetValue 的使用:

var foo = 1;

var fooReference = {
    base: EnvironmentRecord,
    name: 'foo',
    strict: false
};
GetValue(fooReference) // 1;

GetValue 返回对象属性真正的值,但是要注意:

调用 GetValue,返回的将是具体的值,而不再是一个 Reference

具体分析


让我们一步一步看:

  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)());
ECMASCRIPT 的类型分为语言类型和规范类型。
  • ECMAScript 语言类型是开发者直接使用 ECMAScript 可以操作的。其实就是我们常说的Undefined, Null, Boolean, String, Number, 和 Object。
  • ECMAScript 规范中还有一种只存在于规范中的类型,它们的作用是用来描述语言底层行为逻辑。

多说一句

尽管我们不可能去确定每一个this的指向都从规范的角度去思考,久而久之,我们就会总结各种情形来告诉大家这种情形下this的指向,但是能从规范的角度去看待this的指向,绝对是一个不一样的角度,该文有不严谨的地方,还请大神指正!

如何确定this值

关于 Reference 讲了那么多,为什么要讲 Reference 呢?到底 Reference 跟本文的主题 this 有哪些关联呢?如果你能耐心看完之前的内容,以下开始进入高能阶段。
看规范 11.2.3 Function Calls:
这里讲了当函数调用的时候,如何确定 this 的取值。
只看第一步、第六步、第七步:

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).
  1. Else, Type(ref) is not Reference.
    a. Let thisValue be undefined.

翻译一下就是:
1、将MemberExpression的结果赋值给ref
2、判断ref是不是一个Reference类型
3、如果ref是Reference,并且IsPropertyReference(ref)是true,那么this的值为GetBase(ref)
4、如果ref是Reference,并且base value值是Enviroment Recored,那么this值为ImplicitThisValue(ref)
5、如果ref不是reference,那么this的值为undefined

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!

其中重点是便是规范类型中的 Reference 类型。它与 this 的指向有着密切的关联。

这里的 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
    };

而且规范中还提供了获取 REFERENCE 组成部分的方法,比如 GETBASE 和 ISPROPERTYREFERENCE。

  • GetBase(reference),返回的是reference的值
  • IsPropertyReference(reference),如果 base value 是一个对象,就返回true。
  • GetValue,GetValue 返回对象属性真正的值,但是要注意:调用 GetValue,返回的将是具体的值,而不再是一个 Reference

深入系列

JavaScript深入系列预计写十五篇左右,旨在帮大家捋顺JavaScript底层知识,重点讲解如原型、作用域、执行上下文、变量对象、this、闭包、按值传递、call、apply、bind、new、继承等难点概念,与罗列它们的用法不同,这个系列更注重通过写demo,捋过程、模拟实现,结合ES规范等方法来讲解。

所有文章和demo都可以在github上找到。如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎star,对作者也是一种鼓励。

本系列:

  1. JavaScirpt 深入之从原型到原型链
  2. JavaScript 深入之词法作用域和动态作用域
  3. JavaScript 深入之执行上下文栈
  4. JavaScript 深入之变量对象
  5. JavaScript 深入之作用域链

    1 赞 收藏 评论

澳门金莎娱乐网站 1

具体分析

(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 的结果是一样的。

当函数调用时,如何确定THIS的值?

1.计算MemberExpression的结果赋给ref
2.判断ref是不是一个Reference类型
a.如果ref是Reference,并且ref的base value是一个对象,那么 this 的值为 base value
如果 ref 是 Reference,并且 base value 值是 Environment Record, 那么this的值为 ImplicitThisValue(ref)即为undefind
b.如果 ref 不是 Reference,那么 this 的值为 undefined
举个例子:

                    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 的判断流程走:该值是 Reference 类型,然后执行IsPropertyReference(ref),如果ref的base value是一个对象则返回true,那么 this 的值为 base value 即这个foo

实例3,4,5中,MemberExpression计算结果分别为(foo.bar=foo.bar),(false||foo.var),(f00,bar,foo,bar)

根据规范,逗号操作符,逻辑或操作符和赋值操作符都会执行:

3.Let rval be GetValue(ref).

因为使用了 GetValue,所以返回的不是 Reference 类型,this 为 undefined

this 为 undefined,非严格模式下,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都指向了全局对象


1、将MemberExpression的结果赋值给ref

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

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

(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 的时候,其值会被隐式转换为全局对象

3.常见问题

1.如何改变this的指向?


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.

我们知道foo.bar的Reference值为:

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

借下来按照2.1的判断流程走。

2.1 如果 ref 是 Reference,并且 IsPropertyReference(ref) 是 true, 那么 this 的值为 GetBase(ref)
该值是reference类型,那么 IsPropertyReference(ref) 是多少呢?
根据前面的只是,如果base value是一个对象,结果返回true。base value为foo,是一个对象,所以IsPropertyReference(ref)结果为true。
这个时候我们就可以确定this的值了: this = GetBase(ref);
GetBase可以获得base value值,这个例子中是foo,所以this的值就是foo,示例1的结果就是2!

(foo.bar)()
看示例2:console.log((foo.bar)());
foo.bar被()包住,查看规范11.1.6The 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.

翻译:

返回执行Expression的结果,它可能是Reference类型。
这一算法并不会作用GetValue于执行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

(false || foo.bar)()

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

2.Let lval be GetValue(lref).

因为使用了 GetValue,所以返回的不是 Reference 类型,this 为 undefined

4.解决方案

答:可以使用call或者apply的方法:

                // 一个对象可以作为call和apply的第一个参数,并且this会被绑定到这个对象。
                var obj = {a: 'Custom'};

                // 这个属性是在global对象定义的。
                var a = 'Global';

                function whatsThis(arg) {
                  console.log(this.a) // this的值取决于函数的调用方式
                }

                whatsThis();          // 'Global'
                whatsThis.call(obj);  // 'Custom'
                whatsThis.apply(obj); // 'Custom'

结果揭晓

所以结果就是

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 会报错。

(foo.bar, foo.bar)()

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

计算第二步:

2.Call GetValue(lref).

因为使用了 GetValue,所以返回的不是 Reference 类型,this 为 undefined

5.编码实战


最简单的情况
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。

揭晓结果

所以最后一个例子的结果是:

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 会报错。

6.扩展思考

以下代码的this的指向?

                    function Foo(){
                        getName = function(){
                            console.log(1);
                            };
                        return this
                    }

                    function getName(){
                        console.log(5);
                    }

                    Foo().getName();

at last

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

var value = 1;

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

此外,又如何确定调用函数的对象是谁呢?

补充

最最后,忘记了一个最最普通的情况:

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。

7.参考文献

参考一:JavaScript深入之从ECMAScript规范解读this

参考一:this


8.更多讨论

Q1:构造函数的this指向?
A1:构造函数的this指向实例对象

Q2:除了call,applay还有其他改变this指向的方法吗?
A2:还可以使用new关键字和bing()方法改变this的指向

Q3:reference是什么?
A3:reference类型是js中的规范类型,是用来描述底层行为逻辑的类型,并不存在于实际的js代码中


视频
PPT

本文由澳门金莎娱乐网站发布于澳门金莎娱乐网站,转载请注明出处:深入之从,从ECMAScript规范解读this

关键词:

12款强大的,10个HTML5素描及绘画设计工具

10个HTML5素描及绘画设计工具 2011/03/26 · HTML5 · 来源:smashinghub    · HTML5 Mr. Doob’s Harmony 非常适合随手绘制勾勒图像...

详细>>

浅拷贝与深拷贝详解澳门金莎娱乐网站:,深入

JavaScript 深入之参数按值传递 2017/05/23 · JavaScript· 参数 原文出处: 冴羽    ES6 变量声明与赋值:值传递、浅拷贝与...

详细>>

HTML5会成为移动应用的天敌吗,H5是原生app的天敌

HTML5会成为移动应用的天敌吗? 2011/07/22 · HTML5 ·HTML5 好几年来,我们一直在谈论有关应用程序的众多用途。手机应用...

详细>>

19本Web开发与设计人员应该收藏的免费电子书,

Web开发人员应有的15本免费电子书 2011/07/05 · HTML5,JavaScript · 2评论 ·HTML5,Javascript 当今互联网已经成为每一个人的信息...

详细>>