You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The production CallExpression : MemberExpression Arguments is evaluated as follows:
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.
The following steps are performed when control enters the execution context for function code contained in function object F, a caller provided thisArg, and a caller provided argumentsList:
...
2.Else if thisArg is null or undefined, set the ThisBinding to the global object.
An Identifier is evaluated by performing Identifier Resolution as specified in 10.3.1. The result of evaluating an Identifier is always a value of type Reference.
所以接下来执行6.b: 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).
The production MemberExpression : MemberExpression [ Expression ] is evaluated as follows:
8.Return a value of type Reference whose base value is baseValue and whose referenced name is propertyNameString, and whose strict mode flag is strict.
https://wolfdu.fun/post?postId=5a5079d5b890b156d0fae63d
this指向初体验
初次系统学习了解this的指向问题是在阅读You-Dont-Know-JS的时候,当时感觉就是一个this的指向问题就要记住4种规则去判定,还要根据优先级去使用对应的规则,感觉心好累 (눈_눈)
不过呢,至少可以纠正this指向的一些基本错误,同时慢慢接近this指向问题的真相。可以参考我之前记录的相关笔记,这里就不再赘述了About This&Comprehensive analysis This
通过EC摸索this方向
不过呢,在我们了解ja的执行上下文(EC)之后,我们视乎可以去跟本质的去观察this的指向问题。
如果你还不了解JavaScript的执行上下文<-可以戳这里 o‿≖✧
画个简单的流程图描述下之前总结执行上下文:

可以发现在之前梳理EC的时候,我并没有花力气去解释this的指向相关问题,为啥呢?是因为当时我的思路也不太清晰,所以这里单独在进行梳理。
那么我们要重点关注就是创建EC时的this指向的过程了。
其实从图中我就可以发现:**this的指向,是在函数被调用的时候确定的,也就是在创建EC阶段。**因此当函数的调用方式不同时,this的指向就发生了变化。
我们可以看从一个简单的荔枝观察this指向:
是不是感觉变化多端呢?
我们再看一个荔枝:
当我在
foo
中改变this的指向时,会抛出异常,也就是函数的this指向一旦确定后,就不能再改变了。全局对象中this
在分析全局执行上下文的时候,我们知道,在全局执行上下文中,global object就是变量对象(variable object)。在浏览器端,global object被具象成window对象,也就是说 global object === window === 全局执行上下文的variable object。因此global object对于程序而言也是唯一可读的variable object。
我们可以在全局作用域下看看
所以在全局作用域中this指向的就是全局对象,在浏览器中指向window,在node中指向global。
函数中this指向
1. 函数直接调用
从最简单的场景开始:
函数直接被调用,这个时候我们可以发现this指向了全局对象。
2. 作为对象方法调用
接下来复杂一点的栗子:
可以想一想,这里会输出什么样的结果呢?
我们先看看
obj.fn()
这里会输出2,也就是说这里的this指向当前调用函数的对象obj
。但是让人疑惑的是
obj.c
为什么是11呢?其实很好理解,我们使用{}是不会形成新的作用域的,所以为c赋值时的
this.a
中的this指向是全局对象。3. 严格模式下的this指向
通过这个栗子我们看看严格模式下的区别:
fn()
为独立调用函数,在严格模式下this指向undefined,但是在非严格模式中,当this指向undefined时,它会被自动指向全局对象。window.fn()
为全局变量调用fn
,函数内部的this指向window,所以这里会输出全局对象window。结合栗子的结果思考一下,严格模式与非严格模式区别。
到这里,会不会觉得this的指向问题是不是有迹可循了呢?
可以思考下面的代码会输出什么结果呢?
call,apply,bind中的this指向
这里相关的方法前面的文章已经模拟实现过了,如果对于以上几种方法中的this指向还不是很清楚可以猛捶JavaScript模拟实现call,apply,bind等方法,
new运算符中的this指向
不好意思,new运算符的this指向也被封装到了这里new运算符模拟实现,看过应该不会再对new的实现和this指向有疑问了。
分割线。。。
我们可以发现,从以上角度(全局下,函数独立调用,对象方法调用,new,call,apply,bind)探索this指向的文章其实有很多,感觉也是千篇一律,总结下来其实也是对应的场景和规则。那么为什么会有这些场景和规则呢?
既然有规则,那么我去翻翻ECMAScript规范吧,这里还有英文版规范。
从规范的角度去解读this的指向
我们先看一看规范中涉及到this指向的相关知识点,也是查看规范是所需要的背景知识。
涉及this指向的规范概览
Types
第8章 Types:
我们先要清楚这两种类型:
这里我们要区别就是,规范类型可用来描述 ECMAScript 表式运算的中途结果,但是这些值不能存成对象的变量或是 ECMAScript 语言变量的值。也就是说规范类型它们是为了更好地描述ECMAScript语言的底层行为逻辑才存在的,但并不存在于实际的 js 代码中。
下一个我们要关注的点就是规范类型中的Reference引用类型
Reference
第8.7 Reference
Reference由3部分组成:
其中base value 是 undefined, 一个 Object, 一个 Boolean, 一个 String, 一个 Number, 一个 environment record 中的任意一个。
我们用实例来模拟下Reference
还需要了解Reference相关的几个方法
MemberExpression
第11.2 MemberExpression
示例:
这里可以理解MemberExpression就是括号左边的部分(눈_눈)
Function Calls
第11.2.3 Function Calls
这里只用关注1,6,7步
简单解释下:
1.将MemberExpression的执行结果赋值给ref
6.判断ref类型,如果类型为Reference,那么判断
IsPropertyReference(ref)
为true那么this指向GetBase(ref)
,否则this值为undefined
7.如果ref类型不是Reference那么this值为
undefined
Entering Function Code
第10.4.3 Entering Function Code
当控制流进入函数代码的执行阶段时,对this值的处理。
我们重点关注第二步
GetValue(v)
8.7.1 GetValue(v)
关于GetValue我们只需要明白一点:如果是reference传入,会返回一个普通类型出来。比如 foo 为reference,通过GetValue之后就是一个普通的 object,也就是 foo 对应的 js 类型本身。
通过一个示例模拟下:
通过以上的理论梳理我们从函数调用过程大概可以得到如下流程:

准备工作就到这里啦,我们开车吧。
探索this指向
在了解了以上关于this指向规范的理论之后,你一定很懵逼吧,没看太懂没关系,结合实例一步一对应就会慢慢看到this在像你招手了。
我们正式开始探索this指向。
最简单的例子->函数的独立调用开始:
先看第一步
将执行MemberExpression的结果赋值给ref
我们之前已经知道了MemberExpression就是foo,那么接下来就是执行foo标识符,那么这个foo的引用过程是什么样的呢?我们来看规范怎么说吧。
Identifier Reference(标识符引用)
第11.1.2 Identifier Reference
这里直接告诉我们Identifier 的执行遵循 10.3.1 所规定的标识符查找。标识符执行的结果总是一个 Reference 类型的值。
既然都告诉我们章节了,我们就去看一看。
Identifier Resolution(标识符解析)
第10.3.1 Identifier Resolution
env为当前的词法环境,然后第三步以env,Identifier,strict为参数调动GetIdentifierReference函数(;´༎ຶД༎ຶ`) 有完没完,都看到这里了就,硬这头皮继续看下去吧,看完了我们再炸锅~
GetIdentifierReference(lex, name, strict)
第10.2.2.1 GetIdentifierReference(lex, name, strict)
我们发现最终会返回一个Reference,并且基值(base value)为envRec,name为参数中name,strict为参数strict
咻~终于有个结果了,第一步也就执行完了:
接下来执行第6步:
6. If
Type(ref)
isReference
, then执行
Type(ref)
,我们知道这里的ref是Reference那么接下来执行
IsPropertyReference(ref)
:上文中提到过的Reference方法,这里先执行
HasPrimitiveBase(ref)
为false
,那么IsPropertyReference(ref)
也为false
所以接下来执行
6.b
: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).
ImplicitThisValue
第10.2.1.1.6 ImplicitThisValue
那么我们知道这里
thisValue = undefined
,那么执行foo()
的this = undefined
最后一步
Entering Function Code
10.4.3 Entering Function Code
this = global = window
٩(๑ᵒ̴̶͈᷄ᗨᵒ̴̶͈᷅)و功夫不负有心人,能到这里的都是勇士!!!
通过上面独立函数调用的分析过程,我们继续分析其他常见情况:
对象方法调用函数:
首先执行MemberExpression将其结果赋值给ref,此时MemberExpression为
foo.bar
我们看一看执行属性访问的过程:Property Accessors
11.2.1 Property Accessors
这里我们就只关注第8步了,我们可以知道返回的是一个Reference类型
接下来轻车熟路,判断是否是Reference类型?
我们已经知道ref是Reference类型了。
接下来判断ref的基值类型
那么接下来执行
IsPropertyReference(ref)
结果为true
:那么此时的this值为ref的基值
thisValue = foo
是不是so easy ,所以当执行
foo.bar()
时bar中的this是指向foo的。到这里我们已经知道了大致套路了。
最后再看一种情况
执行MemberExpression将其结果赋值给ref,执行语句为
fn = foo.bar
,我们需要了解赋值语句执行过程:Simple Assignment ( = )
11.13.1 Simple Assignment ( = )
简答解释下,这里需要对右边的表达式执行结果执行GetValue(rref)方法,前面我们已经了解到了右边表达式(
foo.bar
)执行结果是一个Reference,那么GetValue(rref)执行将返回一个非Reference类型结果。执行函数调用
我们得到
thisValue = undefined
然后进入函数执行阶段:
10.4.3 Entering Function Code
this = global = window
٩(๑ᵒ̴̶͈᷄ᗨᵒ̴̶͈᷅)و吼啦~当你遇到你无法用理解的this指向问题时不妨试试从规范的角度去探寻一下,是一个很不错的选择哦。
小结
这里我们从传统的this解读视角对比ECMAScript规范视角解读this指向,也算是从现象到本质的一种探索了。
不得不说从规范去寻找this的指向过程要麻烦太多相对也要晦涩一点,但是当你耐心一点一点理顺了之后,会有新的收获,同时规范显得也没那么神秘难懂。
相信在你明白本文中所有内容的时候,你对于的this指向的理解应该清晰明了了。
不过无法理解规范相关的内容,也不用着急,过一段时间后在来翻阅,或许会有新的收获。
参考文章
根治JavaScript中的this-ECMAScript规范解读
JavaScript深入之从ECMAScript规范解读this
若文中有知识整理错误或遗漏的地方请务必指出,非常感谢。如果对你有一丢丢帮助或引起你的思考,可以点赞鼓励一下作者=^_^=
The text was updated successfully, but these errors were encountered: