前言
前些天看到一篇大佬写的 《JavaScript深入之从ECMAScript规范解读this》
,虽然规范看起来是有点费力,但认真看完后,有点突然悟了的感觉,所以在此记录并疏理一下内容。在此之前,对this的理解也停留在表层,仅限于那种总结好的this指向模板,但若要分析为啥,也说不出个所以然来,而且有些时候那些模板也并不好用,如大佬文章里的例子:
var value = 1;
var foo = {
value: 2,
bar: function () {
return this.value;
}
}
console.log((foo.bar = foo.bar)());
console.log((false || foo.bar)());
复制代码
以上的this指向该如何解释呢?
所以,我觉得还是有必要了解一下规范的,话不多说,进入正题。
说明:此文的举例基本都是借用大佬文章里的例子。
正文
先来看一下规范中函数的this是如何确定的,如图:
上面有8个点,为了便于理解,简单地用中文描述一下这8个点:
- 令 ref的结果为 MemberExpression 解释执行的结果。
- 令 func 为 GetValue(ref)。
- 令 argList 为解释执行 Arguments 的结果 , 产生参数值们的内部列表 。
- 如果 Type(func) is not Object ,抛出一个 TypeError 异常 。
- 如果 IsCallable(func) is false ,抛出一个 TypeError 异常 。
- 如果 Type(ref) 为 Reference,那么如果 IsPropertyReference(ref) 为 true,那么 令 thisValue 为 GetBase(ref)。 否则 , ref 的基值是一个 Environment Record, 令 thisValue 为调用 GetBase(ref) 的 ImplicitThisValue 具体方法的结果。
- 否则 , 假如 Type(ref) 不是 Reference. 令 thisValue 为 undefined。
- 返回调用 func 的 [[Call]] 内置方法的结果 , 传入 thisValue 作为 this 值和列表 argList 作为参数列表。
别看有8个点挺多的,但其实重要是只有第 1、6、7 点。不过,直接看的话也还是看不懂的,需要先理解一下上面出现的加粗的概念。
下面就逐一来理解一下。
Types
开始之前,先来看一下规范里的对类型的解释,如图:
重点已经标记出来了,简单来说就是:
ECMAScript的类型分为 语言类型 与 规范类型。
语言类型分为: Undefined, Null, Boolean, String, Number, Object
。也就是我们日常开发使用的。
规范类型有: Reference, List, Completion, Property Descriptor, Property Identifier, Lexical Environment, and Environment Record
。它是用来用算法描述 ECMAScript 语言结构和 ECMAScript 语言类型的。也就是用来描述语言底层行为逻辑,只存在于规范中。
可以看到规范类型中出现了我们将要分析的 Reference, Environment Record。接下来就展开分析理解一下。
Environment Record
这个不是重点,简单知道了解一下这个是什么就可以。规范的10.2有这么两句描述:
A Lexical Environment consists of an Environment Record and a possibly null reference to an outer Lexical Environment.
一个词法环境由一个环境记录项和可能为空的外部词法环境引用构成。
An Environment Record records the identifier bindings that are created within the scope of its associated Lexical Environment.
环境记录项记录了在它的关联词法环境域内创建的标识符绑定情形。
我把它简单理解为用来记录词法环境的信息的。
Reference
先看规范,如图:
可以看到,图里出现了上面8个点里的 GetBase
, IsPropertyReference
。不过这里先不看,后面再看。
先看规范的第一句话,它解释了什么是 Reference
,Reference 是用来说明 delete,typeof,赋值运算符这些运算符的行为。
然后看下它的构成(第二段)
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
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
GetBase(V). Returns the base value component of the reference V.
返回 reference 的 base value。
IsPropertyReference(V). Returns true if either the base value is an object or HasPrimitiveBase(V) is true; otherwise returns false.
如果base value是个对象或 HasPrimitiveBase(V) 是 true,那么返回 true;否则返回 false。
MemberExpression
还是先看规范,如图:
描述如下:
MemberExpression包括:
- 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 其实就是()左边的部分。
ImplicitThisValue
规范如图:
简单来说,就是始终返回 undefined
GetValue
用于从 Reference 类型获取对应值的方法
规范如图:
这里简单模拟 GetValue 的使用
var foo = 1;
var fooReference = {
base: EnvironmentRecord,
name: 'foo',
strict: false
};
GetValue(fooReference) // 1;
复制代码
这个的规范具体内容对我们不重要,主要记住 调用 GetValue,返回的将是具体的值,而不再是一个 Reference。
好了,到这里,已经把出现的概念都解析一遍了,接下来就可以分析,前面提到的函数调用过程的第1,6,7点了。
如何确定this的值
现在再来回顾一下规范的第1,6,7点。
- 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).
7,Else, Type(ref) is not Reference.
Let thisValue be undefined.
描述一下,计算过程 :
-
计算 MemberExpression 的结果赋值给 ref
-
判断 ref 是不是一个 Reference 类型
2.1 如果 Type(ref) 为 Reference,如果 IsPropertyReference(ref) 为 true,那么 this就是 GetBase(ref)。
2.2 如果 Type(ref) 为 Reference, 且 base value是一个 Environment Record,那么this就是 ImplicitThisValue,也就是undefined。
2.3 如果 ref 不是 Reference,那么 this 的值为 undefined.
所以,前面说了铺垫这么多,其实说到底,其实只有下面两步,但前面的概念却不能少,不然理解不了。
- 计算 MemberExpression 的结果赋值给 ref
- 判断 ref 是不是一个 Reference 类型。
至此,我们现在已经可以判断this的值了。
先看一个最普通的情况:
function foo() {
console.log(this)
}
foo();
复制代码
MemberExpression 是 foo,解析标识符,对应规范10.3.1
The result of evaluating an identifier is always a value of type Reference with its referenced name component equal to the Identifier String.
所以会返回一个 Reference 类型的值:
var fooReference = {
base: EnvironmentRecord,
name: 'foo',
strict: false
};
复制代码
走流程2.1
2.1 如果 ref 是 Reference,并且 IsPropertyReference(ref) 是 true, 那么 this 的值为 GetBase(ref)
由于这是的base value 是 environment record ,所以 IsPropertyReference(ref) 为false,接下来走流程2.2。
返回ImplicitThisValue(ref),前面有铺垫ImplicitThisValue始终返回undefined,所以this为向undefined
下面再看其他举例:
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:
console.log(foo.bar());
- 按计算过程1走,计算 MemberExpression 赋值给 ref,在这个示例,MemberExpression计算结果是 foo.bar。那么foo.bar是不是一个 Reference 呢?这里foo.bar进行了一个属性访问操作,我们看下规范 11.2.1 Property Accessors ,它的计算过程如下图:
其它过程可以忽略,这里只看计算过程的最后一点就可以,描述一下就是:
返回一个 Reference,其基值为 baseValue 且其引用名为 propertyNameString, 严格模式标记为 strict.
所以 foo.bar 是一个 Reference,
根据前面的内容,我们知道它的值为
var Reference = {
base: foo,
name: 'bar',
strict: false
};
复制代码
- 按计算过程2.1走
根据前面提到的
2.1 如果 ref 是 Reference,并且 IsPropertyReference(ref) 是 true, 那么 this 的值为 GetBase(ref)
这里的base value 为 foo 是一个对象是,所以 IsPropertyReference(ref) 结果为true,
所以 this = GetBase(ref),即 this 指向 foo。
示例2
console.log((foo.bar)());
foo.bar 被 () 包住,这个属于分组表达式,查看规范 11.1.6 The Grouping Operator
实际上 () 并没有对 MemberExpression 进行计算,所以其实跟示例 1 的结果是一样的。
示例3
(foo.bar = foo.bar)()
这是有赋值操作符,对应的规范11.13.1如下:
主要看第三点,使用了 GetValue,所以返回值不是 Reference,所以按计算过程2.3走,返回的是undefined,this 为 undefined,而在非严格模式下,this 的值为 undefined 的时候,其值会被隐式转换为全局对象。
示例4
(false || foo.bar)()
使用了逻辑或运算,对应规范11.11,其中计算第2步是:
2.Let lval be GetValue(lref).
因为使用了 GetValue,所以返回的不是 Reference 类型,this 为 undefined
示例5
(foo.bar, foo.bar)()
使用了逗号操作符,对应规范11.14:
因为使用了 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 会报错。
总结
以上,仅为本人对自己学习疏理过程的记录,难免有疏漏,欢迎交流。