前言
ECMAScript标准是深入学习JavaScript原理最好的资料,没有其二。
通过增加对ECMAScript语言的理解,理解javascript现象后面的逻辑,提升个人编码能力。
欢迎关注和订阅专栏 重学前端-ECMAScript协议上篇
前戏
提到函数,那必然离不开 this 这个魔头。
函数的调用,说两种经典的模式:一种是作为属性被被调用,另外是直接调用
javascript
var eName = 'global eName';
function log(){
return this.eName
}
const obj = {
log(){
return this.eName;
},
eName: "object eName"
}
log(); // global eName
obj.log(); // object eName
this 是什么
常见说法:this指的是调用函数的那个对象。
从行为上说: 函数执行时从 环境记录 中临时借用的一个值而已。
环境记录上的this绑定关系
这里的 this绑定关系,不等于函数内部语句执行时的this。
你可以理解为 环境记录 提供某个值可以作为函数语句执行的 this 使用。
- 函数执行会在准备阶段,会根据条件在 函数执行环境 记录上 创建this的绑定关系。
- 特定的类型的环境记录上存在 this 的绑定关系
- 可以通过
HasThisBinding()
来判断是有this的绑定关系 - 可以通过
GetThisBinding()
来获取this的绑定关系
- 可以通过
- 函数执行时,如需要会去从环境记录中查找this对应的值, 这些值就是前面提到的绑定关系对应的值
可还记得环境记录的类型
- 申明环境记录 Declarative Environment Record
- 对象环境记录 Object Environment Record
- 函数环境记录 Function Environment Record
- 全局环境记录 Global Environment Record
- 模块环境记录 Module Environment Record
环境记录类型 | 是否可以有this绑定 | 保存this绑定的属性 | 值说明 |
---|---|---|---|
申明环境记录 | ❌ | ||
对象环境记录 | ❌ | ||
函数环境记录 | ✔️ | [[ThisValue]] |
|
全局环境记录 | ✔️ | [[GlobalThisValue]] |
浏览器中等于全局对象 |
模块环境记录 | ✔️ | undefined |
通过表格可以得知
- 有 this 绑定关系有函数环境记录,全局环境记录,模块环境记录
- 能真提供有效某的值作为 函数 this 使用的,只有函数环境记录,全局环境记录
this绑定关系初始化和查找流程
在函数代码代码执行中 使用 this 时,this 其实需要经两个流程
- this 绑定关系初始化
- this 绑定关系查找
类似 标志符绑定,接下来一看看整套流程。
环境记录上的this绑定关系初始化
从上小结可以得知, 提供某有效的值作为 函数 this 使用的,只有函数环境记录,全局环境记录。
全局环境记录
其是在初始化领域 Realm InitializeHostDefinedRealm通过调用 NewGlobalEnvironment ( G, thisValue ) 创建的。


从上面的协议描述和箭头指向可以看出,全局环境的 this 可能和 全局对象 globalThis 的值是不一样的。可以通过下面的代码来验证:
- 浏览器(普通脚本): this 是等于全局对象的

- 浏览器(模块脚本)
javascript
<script type="module">
console.log(this === globalThis)
</script>

- nodejs: this 不等于全局对象

函数环境记录
这还是得从函数调用说起
EvalucateCall

从协议描述可知,这里的 thisValue 的值获取方式方式,大致有三种情况:
-
属性引用记录情况, thisValue等于其归属的对象
如下面的代码, 走If分支,thisValue 等于
obj
对象。javascriptvar obj = { log(){ // ...... } } obj.log()
-
非属性引用记录 + 对象环境记录,thisValue等于with的对象
如下的代码,在调用
log
时, 走Else分支 thisValue的等于obj
对象。javascriptconst obj = { name: "name", log(){ console.log(this.name) } } with(obj){ // with这里关联的是对象环境记录 log(); // name }
-
其他情况
thisValue 等于
undefined
。
这里的thisValue,是一个初步的获取,会作为参数传给[[Call]]
thisArgument参数的值。
[[Call]]
接下来就到了 [[Call]]
的流程。

注意绿色标注部分,函数内部语句真正执行前 ,尝试 给函数环境记录 通过 BindThisValue ( V )绑定 [[ThisValue]]
的值。
- OrdinaryCallEvaluateBody(F, argumentsList) 可能会创建新的环境记录,但是都是 申明环境记录,申明环境记录是没有 this绑定关系的。
- 是尝试, 有可能无需绑定this。 比如 箭头函数, 不绑定。
OrdinaryCallBindThis
重头戏曲在 OrdinaryCallBindThis

函数环境记录的 this 绑定关系的值四种情况:
- 箭头函数,不建立this绑定关系
- 严格模式,不会对 this的值进行额外处理,即不会包装成为Object对象
- 非严格模式
- 如果 this 是 undefined 或者null, 会使用全局对象
- 否则 转为 Object
javascript
// 1 箭头
const fn1 = () => {
}
// 2 严格模式
function fn2(){
"use strcit"
}
// 分严格模式
function fn2(){
console.log(this.length)
}
fn2.call(null)
fn2.call("string");
到此,函数环境记录的this绑定关系完毕,说初始化,接下来一起看看查找。
查找this
谁来this
执行上下文
查找逻辑
执行上下文通过 ResolveThisBinding可以帮函数找到 其应该使用的 this的值。
- 通过 GetThisEnvironment 找到有this绑定的环境记录 env
- 环境记录
env.GetThisBinding()
获取 this的值
GetThisEnvironment 查找有this的环境记录
通过 GetThisEnvironment 找到有this绑定的环境记录 env
- 从环境记录的词法环境开始找
- 一层一层往外面的环境记录找,通过
HasThisBinding()
判断是不是有绑定关系 - 最外层是 全局环境记录兜底

各种环境记录,HasThisBinding()的返回值:
环境记录类型 | HasThisBinding() 返回值 | 保存this绑定的属性 | 值说明 |
---|---|---|---|
申明环境记录 | false | ||
对象环境记录 | false | ||
函数环境记录 | true: 非箭头函数 false: 箭头函数 | [[ThisValue]] |
|
全局环境记录 | true | [[GlobalThisValue]] |
浏览器中等于全局对象 |
模块环境记录 | true | undefined |
env.GetThisBinding
环境记录上取值
- 通过 环境记录
env.GetThisBinding()
获取 this的值, 因环境记录类型不同,获取方式也有所区别,不涉及模块脚本的话,能提供this的环境记录也只有 函数环境记录和全局环境记录。


基本的理论知识已经具备了,就通过示例一起跟着协议走一遭,来更好的认识和理解这个恶魔this。
非严格模式 示例
全局代码函数调用
代码
javascript
var eName = 'global eName';
function log(){
return this.eName
}
log();
函数环境记录上的this绑定
log()
调用时
- EvalucateCall
log引用记录所在环境记录不是对象环境记录,且不是属性引用记录, 传入[[Call]]
的thisArgument
参数为 undefined [[Call]]
传给 OrdinaryCallBindThis的thisArgument
参数为 undefined- OrdinaryCallBindThis
是非严格模式,而thisArgument
参数为 undefined,使用全局对象的[[GlobalThisValue]]
, 而在浏览器环境中 其等于 全局对象。
函数环境记录上this绑定为全局对象。
函数环境记录
最后的环境记录如下:

查找this绑定

属性调用
代码
javascript
const obj = {
log(){
return this.eName;
},
eName: "object eName"
}
obj.log(); // object eName
函数环境记录上的this绑定
log()
调用时
- EvalucateCall
log引用记录是属性引用,传入[[Call]]
的thisArgument
参数为obj
[[Call]]
传给 OrdinaryCallBindThis的thisArgument
参数为obj
- OrdinaryCallBindThis
thisArgument
参数为obj
函数环境记录上this绑定为obj
。
函数环境记录
最后的环境记录如下:
因为 obj 是const申明,是保存在全局环境记录的申明环境记录中的。

查找this绑定

全局代码箭头函数
代码
javascript
var eName = 'global eName';
var log = () => {
return this.eName
}
log(); // 'global eName'
函数环境记录上的this绑定
log()
调用时
- EvalucateCall log引用记录所在环境记录不是对象环境记录,且不是属性引用记录, 传入
[[Call]]
的thisArgument
参数为 undefined [[Call]]
传给 OrdinaryCallBindThis的thisArgument
参数为 undefined- OrdinaryCallBindThis
是非严格模式,函数的[[ThisMode]]
的值为lexical
, 直接返回 。不会在函数环境记录上创建this绑定关系。
不会在函数环境记录上创建this绑定关系。
函数环境记录
最后的环境记录如下:

查找this绑定

- 当函数是箭头函数,因其创建的函数环境记录是没有 this绑定关系的。 判断逻辑就是
[[ThisBindingStatus]]
的值。

- 全局环境记录获取This的值逻辑很简单,直接取属性
[[GlobalThisValue]]
,在浏览器环境中其等于全局对象。

属性箭头函数
代码
javascript
var obj = {
log:() => {
return this.eName
},
eName: "object eName"
};
obj.log(); // undefined
函数环境记录上的this绑定
log()
调用时
- EvalucateCall
log引用记录所在环境记录不是对象环境记录,且不是属性引用记录, 传入[[Call]]
的thisArgument
参数为 undefined [[Call]]
传给 OrdinaryCallBindThis的thisArgument
参数为 undefined- OrdinaryCallBindThis
是非严格模式,函数的[[ThisMode]]
的值为lexical
, 直接返回 。不会在函数环境记录上创建this绑定关系。
不会在函数环境记录上创建this绑定关系。
函数环境记录
最后的环境记录如下:

查找this绑定

这种情况和全局代码箭头函数的情况基本是一致的。 根本原因就是 [[ThisMode]]
是跟着函数本身走的,因函数调用而产生的函数环境记录都不会创建 this绑定关系。
严格模式
全局代码函数调用
代码
javascript
var eName = 'global eName';
function log(){
"use strict"
return this.eName
}
log(); // Uncaught TypeError: Cannot read properties of undefined (reading 'eName')
函数环境记录上的this绑定
log()
调用时
- EvalucateCall
log引用记录所在环境记录不是对象环境记录,且不是属性引用记录, 传入[[Call]]
的thisArgument
参数为 undefined [[Call]]
传给 OrdinaryCallBindThis的thisArgument
参数为 undefined- OrdinaryCallBindThis
是严格模式,而thisArgument
参数为 undefined
函数环境记录上this绑定的值为undefined
。
函数环境记录
最后的环境记录如下:

查找this绑定
最终查找 this的值为 undefined
, 固本例会报错

属性调用
代码
javascript
const obj = {
log(){
"use strict"
return this.eName;
},
eName: "object eName"
}
obj.log(); // object eName
函数环境记录上的this绑定
log()
调用时
- EvalucateCall
log引用记录是属性引用,传入[[Call]]
的thisArgument
参数为obj
[[Call]]
传给 OrdinaryCallBindThis的thisArgument
参数为obj
- OrdinaryCallBindThis
thisArgument
参数为obj
函数环境记录上this绑定为obj
。
函数环境记录
最后的环境记录如下:
因为 obj 是const申明,是保存在全局环境记录的申明环境记录中的。

查找this绑定
this的值为 obj

全局代码箭头函数
代码
javascript
var eName = 'global eName';
var log = () => {
"use strict";
return this.eName
}
log(); // 'global eName'
函数环境记录上的this绑定
log()
调用时
- EvalucateCall
log引用记录所在环境记录不是对象环境记录,且不是属性引用记录, 传入[[Call]]
的thisArgument
参数为 undefined [[Call]]
传给 OrdinaryCallBindThis的thisArgument
参数为 undefined- OrdinaryCallBindThis
是非严格模式,函数的[[ThisMode]]
的值为lexical
, 直接返回。不会在函数环境记录上创建this绑定关系。
不会在函数环境记录上创建this绑定关系。
函数环境记录
最后的环境记录如下:

查找this绑定
this最终等于全局对象。
属性箭头函数
代码
javascript
var obj = {
log:() => {
"use strict"
return this.eName
},
eName: "object eName"
};
obj.log(); // undefined
函数环境记录上的this绑定
log()
调用时
- EvalucateCall
log引用记录所在环境记录不是对象环境记录,且不是属性引用记录, 传入[[Call]]
的thisArgument
参数为 undefined [[Call]]
传给 OrdinaryCallBindThis的thisArgument
参数为 undefined- OrdinaryCallBindThis
是非严格模式,函数的[[ThisMode]]
的值为lexical
, 直接返回。不会在函数环境记录上创建this绑定关系。
不会在函数环境记录上创建this绑定关系。
函数环境记录
最后的环境记录如下:

查找this绑定
this最终的值等于全局对象。

其他
这里还未提及到 Function.protoype.call
和 Function.prototype.apply
, 这就是另外一个话题。
这里还未提及到 Function.prototype.bind
,这是另外一个话题。