ECMAScript 函数this全解析 上

前言

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 使用。

  1. 函数执行会在准备阶段,会根据条件在 函数执行环境 记录上 创建this的绑定关系。
  2. 特定的类型的环境记录上存在 this 的绑定关系
    1. 可以通过 HasThisBinding()来判断是有this的绑定关系
    2. 可以通过 GetThisBinding()来获取this的绑定关系
  3. 函数执行时,如需要会去从环境记录中查找this对应的值, 这些值就是前面提到的绑定关系对应的值

可还记得环境记录的类型

环境记录类型 是否可以有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对象。

    javascript 复制代码
    var obj = {
      log(){
        // ......
      }
    }
    
    obj.log()
  • 非属性引用记录 + 对象环境记录,thisValue等于with的对象

    如下的代码,在调用log时, 走Else分支 thisValue的等于obj对象。

    javascript 复制代码
    const obj = {
      name: "name",
      log(){
        console.log(this.name)
      }
    }
    
    with(obj){   // with这里关联的是对象环境记录
      log();     // name
    }
  • 其他情况

    thisValue 等于 undefined

这里的thisValue,是一个初步的获取,会作为参数传给[[Call]]thisArgument参数的值。

[[Call]]

接下来就到了 [[Call]]的流程。

注意绿色标注部分,函数内部语句真正执行前尝试 给函数环境记录 通过 BindThisValue ( V )绑定 [[ThisValue]]的值。

  1. OrdinaryCallEvaluateBody(F, argumentsList) 可能会创建新的环境记录,但是都是 申明环境记录,申明环境记录是没有 this绑定关系的。
  2. 是尝试, 有可能无需绑定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的值。

  1. 通过 GetThisEnvironment 找到有this绑定的环境记录 env
  2. 环境记录 env.GetThisBinding() 获取 this的值

GetThisEnvironment 查找有this的环境记录

通过 GetThisEnvironment 找到有this绑定的环境记录 env

  • 从环境记录的词法环境开始找
  • 一层一层往外面的环境记录找,通过 HasThisBinding() 判断是不是有绑定关系
  • 最外层是 全局环境记录兜底

各种环境记录,HasThisBinding()的返回值:

环境记录类型 HasThisBinding() 返回值 保存this绑定的属性 值说明
申明环境记录 false
对象环境记录 false
函数环境记录 true: 非箭头函数 false: 箭头函数 [[ThisValue]]
全局环境记录 true [[GlobalThisValue]] 浏览器中等于全局对象
模块环境记录 true undefined

env.GetThisBinding环境记录上取值

  1. 通过 环境记录env.GetThisBinding() 获取 this的值, 因环境记录类型不同,获取方式也有所区别,不涉及模块脚本的话,能提供this的环境记录也只有 函数环境记录和全局环境记录。

函数环境记录 GetThisBinding

全局环境记录 GetThisBinding

基本的理论知识已经具备了,就通过示例一起跟着协议走一遭,来更好的认识和理解这个恶魔this。

非严格模式 示例

全局代码函数调用

代码

javascript 复制代码
var eName = 'global eName';
function log(){
  return this.eName
}

log();

函数环境记录上的this绑定

log()调用时

  1. EvalucateCall
    log引用记录所在环境记录不是对象环境记录,且不是属性引用记录, 传入 [[Call]]thisArgument参数为 undefined
  2. [[Call]]
    传给 OrdinaryCallBindThisthisArgument参数为 undefined
  3. OrdinaryCallBindThis
    是非严格模式,而thisArgument参数为 undefined,使用全局对象的 [[GlobalThisValue]], 而在浏览器环境中 其等于 全局对象。

函数环境记录上this绑定为全局对象。

函数环境记录

最后的环境记录如下:

查找this绑定

属性调用

代码

javascript 复制代码
const obj = {
  log(){
   return this.eName;
  },
  eName: "object eName"
}
obj.log();  // object eName

函数环境记录上的this绑定

log()调用时

  1. EvalucateCall
    log引用记录是属性引用,传入 [[Call]]thisArgument参数为 obj
  2. [[Call]]
    传给 OrdinaryCallBindThisthisArgument参数为 obj
  3. OrdinaryCallBindThis thisArgument参数为 obj

函数环境记录上this绑定为obj

函数环境记录

最后的环境记录如下:

因为 obj 是const申明,是保存在全局环境记录的申明环境记录中的。

查找this绑定

全局代码箭头函数

代码

javascript 复制代码
var eName = 'global eName';
var log = () => {
  return this.eName
}

log(); // 'global eName'

函数环境记录上的this绑定

log()调用时

  1. EvalucateCall log引用记录所在环境记录不是对象环境记录,且不是属性引用记录, 传入 [[Call]]thisArgument参数为 undefined
  2. [[Call]]
    传给 OrdinaryCallBindThisthisArgument参数为 undefined
  3. 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()调用时

  1. EvalucateCall
    log引用记录所在环境记录不是对象环境记录,且不是属性引用记录, 传入 [[Call]]thisArgument参数为 undefined
  2. [[Call]]
    传给 OrdinaryCallBindThisthisArgument参数为 undefined
  3. 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()调用时

  1. EvalucateCall
    log引用记录所在环境记录不是对象环境记录,且不是属性引用记录, 传入 [[Call]]thisArgument参数为 undefined
  2. [[Call]]
    传给 OrdinaryCallBindThisthisArgument参数为 undefined
  3. 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()调用时

  1. EvalucateCall
    log引用记录是属性引用,传入 [[Call]]thisArgument参数为 obj
  2. [[Call]]
    传给 OrdinaryCallBindThisthisArgument参数为 obj
  3. 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()调用时

  1. EvalucateCall
    log引用记录所在环境记录不是对象环境记录,且不是属性引用记录, 传入 [[Call]]thisArgument参数为 undefined
  2. [[Call]]
    传给 OrdinaryCallBindThisthisArgument参数为 undefined
  3. OrdinaryCallBindThis
    是非严格模式,函数的 [[ThisMode]]的值为 lexical , 直接返回。不会在函数环境记录上创建this绑定关系。

不会在函数环境记录上创建this绑定关系。

函数环境记录

最后的环境记录如下:

查找this绑定

this最终等于全局对象。

属性箭头函数

代码

javascript 复制代码
var obj = {
  log:() => {
    "use strict"
  	return this.eName
  },
  eName: "object eName"
};
obj.log(); // undefined

函数环境记录上的this绑定

log()调用时

  1. EvalucateCall
    log引用记录所在环境记录不是对象环境记录,且不是属性引用记录, 传入 [[Call]]thisArgument参数为 undefined
  2. [[Call]]
    传给 OrdinaryCallBindThisthisArgument参数为 undefined
  3. OrdinaryCallBindThis
    是非严格模式,函数的 [[ThisMode]]的值为 lexical , 直接返回。不会在函数环境记录上创建this绑定关系。

不会在函数环境记录上创建this绑定关系。

函数环境记录

最后的环境记录如下:

查找this绑定

this最终的值等于全局对象。

其他

这里还未提及到 Function.protoype.callFunction.prototype.apply, 这就是另外一个话题。

这里还未提及到 Function.prototype.bind,这是另外一个话题。

相关推荐
高木的小天才4 分钟前
鸿蒙中的并发线程间通信、线程间通信对象
前端·华为·typescript·harmonyos
Danta1 小时前
百度网盘一面值得look:我有点难受🤧🤧
前端·javascript·面试
OpenTiny社区1 小时前
TinyVue v3.22.0 正式发布:深色模式上线!集成 UnoCSS 图标库!TypeScript 类型支持全面升级!
前端·vue.js·开源
dwqqw1 小时前
opencv图像库编程
前端·webpack·node.js
Captaincc2 小时前
为什么MCP火爆技术圈,普通用户却感觉不到?
前端·ai编程
海上彼尚2 小时前
使用Autocannon.js进行HTTP压测
开发语言·javascript·http
阿虎儿3 小时前
MCP
前端
layman05283 小时前
node.js 实战——(fs模块 知识点学习)
javascript·node.js
毕小宝3 小时前
编写一个网页版的音频播放器,AI 加持,So easy!
前端·javascript
万水千山走遍TML3 小时前
JavaScript性能优化
开发语言·前端·javascript·性能优化·js·js性能