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语言进行操作的值。ECMAScript语言类型包括Undefined、Null、Boolean、String、Number和Object。
规范类型对应于在算法中使用的元值,用于描述ECMAScript语言构造和ECMAScript语言类型的语义。规范类型包括Reference、List、Completion、Property Descriptor、Property Identifier、Lexical Environment和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 的指向有着密切的关联。
2. Reference
那什么⼜是 Reference ?
The Reference type is used to explain the behaviour of such operators as delete, typeof, and the assignment operators.
中文翻译:
引用类型(Reference type)用于解释 delete、typeof 和赋值操作符等操作符的行为。
所以 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 flag)。
基础值可以是 undefined、一个对象、一个布尔值、一个字符串、一个数字,或者是一个环境记录(10.2.1)。
基础值为 undefined 表示引用无法解析为绑定。引用的名称是一个字符串。
这段讲述了 Reference 的构成,由三个组成部分,分别是:
base value(基础值);referenced name(引用的名称);strict reference flag(布尔值类型的严格引用标志);
可是这些到底是什么呢?
详细解释一下这三个组成部分:
-
基础值(Base Value):
- 基础值可以是 undefined、一个对象、一个布尔值、一个字符串、一个数字,或者是一个环境记录(Lexical Environment Record)。
- 基础值是引用所指向的对象或值。它表示了在哪个对象上进行了名称绑定。
- 当我们对变量或属性进行操作时,基础值就是我们要操作的对象。
-
引用的名称(Referenced Name):
- 引用的名称是一个字符串,表示在基础值上进行名称绑定的属性名称或变量名。
- 例如,在对象上访问属性或者在作用域中访问变量时,引用的名称就是属性名或变量名。
-
布尔值类型的严格引用标志(Strict Reference Flag):
- 严格引用标志是一个布尔值,用于指示引用是否处于严格模式(strict mode)下。
- 如果引用是严格模式下的,则严格引用标志为 true,否则为 false。
- 严格引用标志的存在是为了支持 ECMAScript 的严格模式,在严格模式下,一些行为会更严格地限制,以提高代码质量和安全性。
举个例⼦:
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。
2.1. GetBase
GetBase(V). Returns the base value component of the reference V.
返回 reference 的 base value。
var foo = 1;
var fooReference = {
base: EnvironmentRecord,
name: 'foo',
strict: false
};
GetValue(fooReference) // 1;
GetValue 返回对象属性真正的值,但是,调⽤ GetValue,返回的将是具体的值,⽽不再是⼀个Reference
2.2. IsPropertyReference
IsPropertyReference(V). Returns true if either the base value is an object or HasPrimitiveBase(V) is true; otherwise returns false.
IsPropertyReference(V) 函数用于判断一个引用 V 是否为属性引用。如果 base value 是⼀个对象,就返回true。
3. 如何确定this的值
关于 Reference 讲了那么多,为什么要讲 Reference 呢?到底 Reference 跟本⽂的主题 this 有哪些关联呢?
- Let ref be the result of evaluating MemberExpression;
- if Type(ref) is Reference, then
- If IsPropertyReference(ref) is true, then
- Let thisValue be GetBase(ref).
- Else, the base of ref is an Environment Record
- Let thisValue be the result of calling the ImplicitThisValue concrete method of GetBase(ref).
- Else, Type(ref) is not Reference.
- Let thisValue be undefined.
让我们理解⼀下:
- 首先,它评估了 MemberExpression(成员表达式) 并将结果存储在变量 ref 中。
- 接下来,判断 ref 是不是⼀个 Reference 类型,如果是引用类型,那么需要进一步判断引用的基础是一个属性引用还是一个环境记录。
- 如果是属性引用(IsPropertyReference(ref) 返回 true),则将 thisValue 设置为引用的基础(即 GetBase(ref) 的返回值)。
- 如果是环境记录,则需要调用 GetBase(ref) 的 ImplicitThisValue 方法来获取 thisValue。
- 如果 ref 不是引用类型,则将 thisValue 设置为 undefined。
进一步理解一下:
- 如果 ref 是
Reference,并且 IsPropertyReference(ref) 是 true, 那么 this 的值为GetBase(ref) - 如果 ref 是
Reference,并且base value值是Environment Record, 那么this的值为ImplicitThisValue(ref) - 如果 ref 不是
Reference,那么this的值为undefined;
4. 具体分析
4.1. 评估 MemberExpression 的结果,赋值给 ref
什么是 MemberExpression呢?
好的,让我用具体的例子来解释 MemberExpression 中的每种形式:
4.1.1.PrimaryExpression(原始表达式):
var x = 10; // 变量赋值是一个原始表达式
var str = "Hello"; // 字符串字面量是一个原始表达式
在这个示例中,变量赋值和字符串字面量都是原始表达式的示例。它们是最基本的表达式形式,不能再分解为更小的表达式。
4.1.2.FunctionExpression // 函数定义表达式
var add = function(a, b) { // 匿名函数赋值给变量是一个函数定义表达式
return a + b;
};
这个示例中,我们定义了一个匿名函数,并将其赋值给变量 add。这是一个函数定义表达式的示例,它允许我们在表达式中定义函数。
4.1.3. MemberExpression [ Expression ] // 属性访问表达式
var obj = { name: "John" };
console.log(obj["name"]); // 使用字符串作为属性名进行属性访问
这个示例中,我们定义了一个对象 obj,然后使用属性访问表达式 obj["name"] 来访问对象的属性。Expression 可以是一个计算得到的属性名。
4.1.4 MemberExpression . IdentifierName // 属性访问表达式
var person = { name: "Alice", age: 30 };
console.log(person.age); // 使用点运算符进行属性访问
在这个示例中,我们使用点运算符 .age 来访问对象 person 的属性。这也是属性访问表达式的一种形式,其中属性名是一个标识符。
4.1.5 new MemberExpression Arguments // 对象创建表达式
function Car(make, model) {
this.make = make; this.model = model;
}
var myCar = new Car("Toyota", "Camry"); // 使用构造函数创建新对象实例
在这个示例中,我们定义了一个构造函数 Car,然后使用 new 关键字和构造函数来创建一个新的对象实例 myCar。这是一个对象创建表达式的示例。
简单理解 MemberExpression其实就是()左边的部分。
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
4.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)());
4.2.1 foo.bar()
MemberExpression 计算的结果是 foo.bar ,那么 foo.bar 是不是⼀个Reference 呢?
根据之前的内容,我们知道该值为:
var Reference = {
base: foo,
name: 'bar',
strict: false
};
接下来按照流程:
- 如果 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。
4.2.2 (foo.bar)()
foo.bar 被 () 包住,
实际上 () 并没有对 MemberExpression 进⾏计算,所以其实跟示例 1 的结果是⼀样的。
4.2.3 (foo.bar = foo.bar)()
让我们逐步解释它的执行过程:
-
(foo.bar = foo.bar):这是一个赋值表达式,它将foo.bar方法赋值给foo.bar属性。但是,赋值表达式的结果是右侧表达式的值,即原始的foo.bar方法。 -
():紧接着对赋值表达式的结果进行了函数调用。- 在非严格模式下,因为没有明确指定
this的值,函数调用中的this会默认指向全局对象(在浏览器环境中通常是window对象)。 - 因此,这里的函数调用会使得
this指向全局对象。
- 在非严格模式下,因为没有明确指定
所以,虽然赋值操作赋值了 foo.bar 方法给 foo.bar 属性,但在函数调用时,this 的值被设为了全局对象。因此,函数内部访问的是全局对象的 value 属性,而全局对象的 value 属性为 1。所以,输出结果为 1。
4.2.4 (false || foo.bar)()
示例4,因为使⽤了 GetValue,所以返回的不是 Reference 类型,this 为 undefined。
4.2.5 (foo.bar, foo.bar)()
看示例5,因为使⽤了 GetValue,所以返回的不是 Reference 类型,this 为 undefined。
4.2.6. 总结
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 值的绑定规则:
-
首先,计算
MemberExpression的值,并将结果赋给ref。 -
如果
ref是一个 Reference 类型:- 如果它是一个属性引用,那么
this的值为属性所属的对象。 - 如果它的基础值是环境记录,那么
this的值由该环境记录的隐式this值确定。
- 如果它是一个属性引用,那么
-
如果
ref不是一个 Reference 类型,则this的值为undefined。