> ## Documentation Index
> Fetch the complete documentation index at: https://opc-43d279b8.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# this 与 Class

> this 四大绑定规则与优先级、手写 new、ES6 class 继承与 super 陷阱，附输出题解题框架。

> `this` 我是被输出题和 React 类组件坑出来的：绑定丢失、箭头函数不绑定、调用位置决定指向，这几点理清后就不容易错。

***

## Q1: this 的四大绑定规则与优先级

**1. this 不是在定义时确定的**

`this` 的核心法则是：**它不是在定义时确定的，而是在代码执行（调用）时动态绑定的**（箭头函数除外）。

**2. 默认绑定: 独立调用**

当函数被直接调用（如 `fn()`）时，`this` 指向全局对象 `window`。在严格模式下，`this` 为 `undefined`。

**3. 隐式绑定: 对象调用**

当函数作为某个对象的方法被调用（如 `obj.fn()`）时，`this` 指向那个最后的调用者对象。

**4. 显式绑定: call/apply/bind**

通过 `call`、`apply`、`bind` 强制指定 `this`。`bind` 属于硬绑定，返回新函数，`this` 无法再被普通的 call/apply 修改。

**5. new 绑定: 构造调用 (优先级最高)**

当使用 `new` 关键字调用函数时，引擎会创建一个全新的空对象，并将 `this` 指向这个新对象。`new` 能覆盖 `bind`。

**this 绑定解决的核心问题：**

```text theme={null}
问题：函数被不同地方调用时，this 指向不同，导致 Bug

同一个函数，obj.fn() 指向 obj，fn() 指向 window。
如果不对 this 进行规范管理，回调、事件处理、Class 方法中
this 丢失是最常见的 Bug 来源。

解决：四大绑定规则 + 箭头函数跳出规则

new > 显式(bind) > 隐式 > 默认，箭头函数直接继承外层 this。
```

> [!TIP]
>
> `this` 就像一个麦克风，谁拿着麦克风（谁调用函数），声音就从谁那里出来。`obj.fn()` 是 obj 拿着麦克风，`this` 就是 obj。把方法赋值给变量 `const fn = obj.greet; fn()` 相当于把麦克风抢过来自己用，`this` 就变成了全局对象。`bind` 就是用胶带把麦克风粘死在某人手上，怎么抢都抢不走。而箭头函数更狠——它根本没有麦克风，它直接借外层人的麦克风用，绑定的胶带都撕不掉。

**this 的优先级漏斗：**

```text theme={null}
是否是 new 调用？
  ↓ 否
是否有 bind/call/apply？
  ↓ 否
是否是被对象点（.）出来调用的？
  ↓ 否
默认调用（window / undefined）

如果是箭头函数，直接跳出漏斗，往外层作用域找！
```

### this 绑定规则图解

四大绑定规则可以用一个漏斗模型来记忆——从优先级最高的规则开始，逐层向下匹配。**箭头函数是特例，直接跳出整个漏斗。**

```text theme={null}
this 绑定规则漏斗（优先级从高到低）：

┌─────────────────────────────────────────────┐
│  ① 是否是 new 调用？                         │
│  ├─ 是 → this = 新创建的空对象               │
│  │        function Person(name) {            │
│  │          this.name = name; // this = {}   │
│  │        }                                  │
│  └─ 否 ↓                                    │
├─────────────────────────────────────────────┤
│  ② 是否有 bind / call / apply？              │
│  ├─ 是 → this = 指定的对象                   │
│  │        fn.call(obj)  // this = obj        │
│  │        fn.bind(obj)  // this = obj (永久) │
│  └─ 否 ↓                                    │
├─────────────────────────────────────────────┤
│  ③ 是否是对象点调用 obj.fn()？               │
│  ├─ 是 → this = obj（最后一个调用者）        │
│  │        obj.inner.fn() // this = obj.inner │
│  └─ 否 ↓                                    │
├─────────────────────────────────────────────┤
│  ④ 默认调用 fn()                            │
│  └─ 非严格模式 → this = window              │
│  └─ 严格模式   → this = undefined           │
└─────────────────────────────────────────────┘

⚡ 箭头函数：直接跳出漏斗！
   在定义时就锁定外层 this，任何规则都无法覆盖。
```

### this 指向的逐步判断流程

遇到任何 `this` 相关的输出题，按照下面的流程图逐层判断，永不踩坑：

```text theme={null}
                    看到函数被调用
                         │
                         ▼
               ┌───────────────────┐
               │ 是不是箭头函数？    │
               └────────┬──────────┘
                    是 / \ 否
                   /     \
                  ▼       ▼
   ┌─────────────────┐   ┌───────────────────────┐
   │ 直接找外层 this  │   │ 是不是 new 调用？      │
   │ 不看调用方式！   │   └───────────┬───────────┘
   │ this = 定义时    │           是 / \ 否
   │ 外层普通函数的   │          /     \
   │ this             │         ▼       ▼
   └─────────────────┘   this={}  ┌──────────────────────┐
                                  │ 有没有 bind/call/apply│
                                  └──────────┬───────────┘
                                          是 / \ 否
                                         /     \
                                        ▼       ▼
                                  this=指定值  ┌───────────────────┐
                                              │ 调用形式 obj.fn()？│
                                              └─────────┬─────────┘
                                                    是 / \ 否
                                                   /     \
                                                  ▼       ▼
                                             this=obj   this=window
                                                       (严格=undefined)
```

### 四种绑定的代码示例（带逐行注释）

```javascript theme={null}
// ========================================
// ① 默认绑定：函数独立调用，this 指向全局
// ========================================
function sayHi() {
  console.log(this); // 非严格模式：window | 严格模式：undefined
}
sayHi(); // 独立调用，没有 obj. 前缀 → 默认绑定

// ========================================
// ② 隐式绑定：通过对象调用，this 指向对象
// ========================================
const user = {
  name: "张三",
  greet() {
    console.log(this.name); // this = user（调用者对象）
  },
};
user.greet(); // '张三' — obj.fn() 形式，隐式绑定

// 隐式绑定丢失：赋值给变量后独立调用
const fn = user.greet; // fn 只是函数引用，不带 user 上下文
fn(); // 取不到 user.name：退化为默认绑定，this = 全局对象
// 严格模式下 this 为 undefined，读取 this.name 会直接 TypeError

// ========================================
// ③ 显式绑定：用 call/apply/bind 强制指定 this
// ========================================
function showName() {
  console.log(this.name);
}
const boss = { name: "老板" };
showName.call(boss); // '老板' — call 立即执行，this = boss
showName.apply(boss); // '老板' — apply 立即执行，参数是数组
const bound = showName.bind(boss); // bind 返回新函数，this 锁定为 boss
bound(); // '老板'

// ========================================
// ④ new 绑定：构造函数调用，this 指向新实例（优先级最高）
// ========================================
function Person(name) {
  this.name = name; // this = 新创建的空对象 {}
}
const p = new Person("新来的");
console.log(p.name); // '新来的'

// ⚡ new 的优先级高于 bind —— 即使函数被 bind 过，new 也能覆盖
const BoundPerson = Person.bind({ name: "被绑定的" });
const instance = new BoundPerson("实例");
console.log(instance.name); // '实例'（不是 '被绑定的'！）
// new 强行把 this 指向新实例，bind 的绑定被忽略
```

**优先级验证流程图：**

```text theme={null}
                    fn() 被调用
                        │
                        ▼
              ┌───────────────────┐
              │ fn 前面有没有 new？│
              └────────┬──────────┘
                   是 / \ 否
                  /     \
                 ▼       ▼
          this = {}  ┌──────────────────────┐
                     │ fn 有没有 bind/call？ │
                     └──────────┬───────────┘
                            是 / \ 否
                           /     \
                          ▼       ▼
                   this = 指定值  ┌───────────────────────┐
                                 │ 调用形式是 obj.fn()？  │
                                 └───────────┬───────────┘
                                         是 / \ 否
                                        /     \
                                       ▼       ▼
                                this = obj   this = window
                                             (严格模式 = undefined)

    特殊情况：如果 fn 是箭头函数，以上全部跳过！
    直接沿词法作用域向外层查找 this。
```

> [!NOTE]
>
> this 的优先级是 new > 显式(bind) > 隐式 > 默认。箭头函数跳出整套规则，直接继承外层 this。

> [card] 陷阱: 隐式绑定丢失
>
> 把对象的方法赋值给变量，然后独立调用：
>
> ```javascript theme={null}
> const obj = {
>   name: "张三",
>   greet() {
>     console.log(this.name);
>   },
> };
> const fn = obj.greet;
> fn(); // 取不到 obj.name（退化为默认绑定）
> setTimeout(obj.greet, 100); // 同理：回调执行时没有 obj. 前缀
> ```

> [card] 破局: 箭头函数的优势
>
> 箭头函数**根本没有自己的 this**！它在被定义的那一刻起，就顺着词法作用域往上找，牢牢捕获外层的 `this`。
>
> ```javascript theme={null}
> const obj = {
>   name: "张三",
>   greet() {
>     setTimeout(() => console.log(this.name), 100);
>   },
> };
> obj.greet(); // "张三"
> ```

***

## Q2: 输出题解题框架——如何快速判断 this 指向

面试中 `this` 输出题出现频率极高，掌握一套固定的分析框架可以做到秒杀。

### 解题四步法

```text theme={null}
看到 this 输出题，按以下步骤分析：

第 1 步：找到函数的定义位置
         └→ 普通函数？箭头函数？Class 方法？

第 2 步：找到函数的调用位置
         └→ 谁调用了它？调用形式是什么？

第 3 步：判断绑定规则
         └→ new？显式？隐式？默认？箭头函数？

第 4 步：确定 this 值，代入执行
         └→ 输出什么？
```

**this 判断速查图：**

```text theme={null}
┌──────────────────────────────────────────────────────────┐
│                    看到一段代码                            │
│                        │                                 │
│                        ▼                                 │
│            ┌───────────────────────┐                     │
│            │ 函数是箭头函数吗？     │                     │
│            └───────────┬───────────┘                     │
│                    是 / \ 否                              │
│                   /     \                                │
│                  ▼       ▼                               │
│    ┌────────────────┐   ┌────────────────────┐           │
│    │ 找外层的 this  │   │ 找调用点            │           │
│    │ 沿作用域链向上 │   │ 用漏斗模型判断      │           │
│    │ 直到非箭头函数 │   │ new > 显式 > 隐式   │           │
│    │ 的 this       │   │ > 默认              │           │
│    └────────────────┘   └────────────────────┘           │
│              │                   │                       │
│              ▼                   ▼                       │
│         ┌─────────────────────────────┐                  │
│         │  确定 this 值，代入函数体    │                  │
│         │  输出结果                    │                  │
│         └─────────────────────────────┘                  │
└──────────────────────────────────────────────────────────┘
```

> [!NOTE]
>
> 找调用点，看形式，判规则，定 this。箭头函数不用看调用点，直接往上找。

### 经典输出题图解：对象方法调用

**题目 1：`obj.fn()` vs `const fn = obj.fn; fn()`**

```javascript theme={null}
const obj = {
  name: "张三",
  greet() {
    console.log(this.name);
  },
};

obj.greet(); // 输出什么？
const fn = obj.greet;
fn(); // 输出什么？
```

```text theme={null}
情况一：obj.greet()  → 隐式绑定

┌──────────┐    .greet()    ┌──────────────┐
│   obj    │ ──────────────→│   greet()    │
│ name:'张三'│               │ this = obj   │
└──────────┘                │ this.name    │
                            │ = '张三'     │
                            └──────────────┘
输出：'张三' ✅


情况二：const fn = obj.greet; fn()  → 默认绑定

┌──────────┐   赋值引用    ┌──────────┐    独立调用     ┌──────────────┐
│   obj    │ ────────────→│   fn     │ ──────────────→│   greet()    │
│ name:'张三'│              │ = greet │                │ this = window│
└──────────┘               └──────────┘                │ window.name  │
                                                        │ = undefined  │
                                                        └──────────────┘
输出：undefined ⚠️

原因：fn = obj.greet 只是复制了函数引用，调用时没有通过 obj. 前缀，
      隐式绑定丢失，退化为默认绑定。
```

> [!NOTE]
>
> 方法赋值给变量后独立调用 = 隐式绑定丢失。这是 this 输出题的头号陷阱。

### 经典输出题图解：回调函数中的 this

**题目 2：`setTimeout(obj.fn, delay)`**

```javascript theme={null}
const obj = {
  name: "李四",
  greet() {
    console.log(this.name);
  },
};

setTimeout(obj.greet, 1000); // 输出什么？
```

```text theme={null}
执行流程图解：

时间线 ──────────────────────────────────────────────→

t=0                    t=1000ms
  │                        │
  ▼                        ▼
┌──────────────┐     ┌──────────────────┐
│ obj.greet    │     │  回调被引擎调用   │
│ (函数引用)   │     │  greet()         │
│ 传给         │ →   │  无 obj. 前缀    │
│ setTimeout   │     │  this = window   │
│ 存入队列     │     │  this.name       │
└──────────────┘     │  = undefined     │
                     └──────────────────┘

输出：undefined

原因：setTimeout 只接收函数引用，不保留 obj 上下文。
      回调执行时是引擎内部独立调用 greet()，不是 obj.greet()。
```

**修复方案对比：**

```javascript theme={null}
// 方案一：箭头函数包裹（推荐）
setTimeout(() => obj.greet(), 1000);
// 箭头函数内调用 obj.greet()，隐式绑定保留

// 方案二：bind 硬绑定
setTimeout(obj.greet.bind(obj), 1000);
// bind 返回新函数，this 被永久锁定为 obj
```

> [!NOTE]
>
> `setTimeout(fn, delay)` 传的是函数引用，执行时丢失调用者上下文。用箭头函数包裹或 `.bind(this)` 修复。

### 经典输出题图解：Class 中的 this 丢失

**题目 3：Class 方法引用丢失**

```javascript theme={null}
class Logger {
  constructor(name) {
    this.name = name;
  }
  log() {
    console.log(this.name);
  }
}

const logger = new Logger("App");
logger.log(); // 输出什么？

const { log } = logger;
log(); // 输出什么？

setTimeout(logger.log, 100); // 输出什么？
```

```text theme={null}
情况一：logger.log() → 隐式绑定 ✅

┌────────────┐  .log()   ┌────────────┐
│  logger    │ ─────────→│   log()    │
│ name:'App' │           │ this = logger │
└────────────┘           │ 输出 'App'  │
                         └────────────┘


情况二：const { log } = logger; log() → 默认绑定 ⚠️

┌────────────┐  解构赋值   ┌──────────┐   独立调用   ┌────────────┐
│  logger    │ ──────────→│   log    │ ───────────→│   log()    │
│ name:'App' │            │ (引用)   │             │ this = window │
└────────────┘            └──────────┘             │ 输出 undefined│
                                                   └────────────┘

情况三：setTimeout(logger.log, 100) → 默认绑定 ⚠️

与情况二同理，setTimeout 接收函数引用后独立调用。


Class 方法的本质：
┌────────────────────────────────────────────┐
│  class Logger {                            │
│    log() { ... }  // 实际在 prototype 上   │
│  }                                         │
│                                            │
│  Logger.prototype.log  ← 方法在这里        │
│  logger.log  ← 从原型链上取到同一个函数    │
│  解构/赋值后 = 只是复制引用，上下文丢失    │
└────────────────────────────────────────────┘
```

**Class 中 this 丢失的标准修复：**

```javascript theme={null}
class Logger {
  constructor(name) {
    this.name = name;
    // 方案一：constructor 中 bind（推荐，只执行一次）
    this.log = this.log.bind(this);
  }
  // 方案二：箭头函数类属性（最简洁，需 Babel 支持）
  log = () => {
    console.log(this.name);
  };
}
```

> [!NOTE]
>
> Class 方法定义在 prototype 上，解构或传参后会丢失 this。用 constructor bind 或箭头函数类属性修复。

### 经典输出题图解：箭头函数中的 this

**题目 4：箭头函数继承外层 this**

```javascript theme={null}
const obj = {
  name: "王五",
  greet() {
    const inner = () => console.log(this.name);
    return inner;
  },
};

const fn = obj.greet();
fn(); // 输出什么？

obj.greet()(); // 输出什么？

const { greet } = obj;
greet()(); // 输出什么？
```

```text theme={null}
分析：箭头函数 inner 没有自己的 this，在 greet() 定义时
      就锁定了 greet 的 this。

情况一：const fn = obj.greet(); fn();

Step 1: obj.greet() → 隐式绑定 → this = obj
Step 2: greet 内定义箭头函数 inner → inner 的 this = greet 的 this = obj
Step 3: fn = inner（返回值）
Step 4: fn() → 独立调用，但 inner 是箭头函数！
        箭头函数不看调用方式，this 永远是定义时的外层 this = obj

┌──────────┐  .greet()  ┌────────────┐  return   ┌──────────┐
│   obj    │ ──────────→│  greet()   │ ─────────→│  inner   │
│          │            │ this = obj │           │ (箭头函数)│
│          │            │ 定义 inner │           │ this = obj│
│          │            │ inner.this │           │ (锁定!)  │
│          │            │ = obj      │           └──────────┘
└──────────┘            └────────────┘                │
                                                      ▼
                                               fn() 独立调用
                                               但箭头函数不在乎！
                                               this 仍然是 obj
输出：'王五' ✅


情况二：const { greet } = obj; greet()();

Step 1: greet 被解构出来 → 独立引用
Step 2: greet() → 独立调用 → greet 的 this = window（默认绑定）
Step 3: 箭头函数 inner 的 this = greet 的 this = window
输出：undefined ⚠️

┌──────────┐  解构     ┌──────────┐  独立调用  ┌────────────┐
│   obj    │ ─────────→│  greet   │ ──────────→│  greet()   │
└──────────┘           │ (引用)   │            │ this = window│
                       └──────────┘            │ 定义 inner │
                                               │ inner.this │
                                               │ = window   │
                                               └────────────┘
输出：undefined

关键：箭头函数锁定的是"外层普通函数的 this"，
      如果外层普通函数的 this 变了，箭头函数也会跟着变！
```

> [!NOTE]
>
> 箭头函数的 this 在定义时锁定为外层普通函数的 this。如果外层 this 丢了，箭头函数也救不了。

### 经典输出题图解：链式调用中的 this

**题目 5：链式调用中 this 的变化**

```javascript theme={null}
const obj = {
  name: "链式",
  setName(name) {
    this.name = name;
    return this;
  },
  getName() {
    return this.name;
  },
  print() {
    console.log(this.name);
    return this;
  },
};

obj.setName("新名字").print().getName(); // 输出什么？
```

```text theme={null}
链式调用的 this 流转图解：

obj.setName('新名字')     .print()          .getName()
      │                      │                   │
      ▼                      ▼                   ▼
┌──────────────┐    ┌──────────────┐    ┌──────────────┐
│ setName()    │    │ print()      │    │ getName()    │
│ this = obj   │ →  │ this = obj   │ →  │ this = obj   │
│ this.name    │    │ console.log  │    │ return       │
│ = '新名字'   │    │ '新名字'     │    │ this.name    │
│ return this  │    │ return this  │    │ = '新名字'   │
│ (返回 obj)   │    │ (返回 obj)   │    └──────────────┘
└──────────────┘    └──────────────┘

输出：'新名字'
返回值：'新名字'

链式调用的关键：每个方法 return this → 返回的是同一个 obj → 下一个方法隐式绑定 this = obj
```

**链式调用 this 丢失的陷阱：**

```javascript theme={null}
const obj = {
  value: 42,
  getValue() {
    return this.value;
  },
};

// 陷阱：解构后链式调用
const { getValue } = obj;
getValue(); // undefined — 隐式绑定丢失

// 陷阱：回调中传递方法
[1, 2, 3].map(obj.getValue); // [undefined, undefined, undefined]
// map 的回调独立调用，this = window
```

> [!NOTE]
>
> 链式调用中 this 不变，前提是每次调用都是 obj.method() 形式。一旦脱离对象独立调用，this 立即丢失。

***

## Q3: 手写 new 操作符的四步走

```javascript theme={null}
function myNew(Constructor, ...args) {
  const obj = Object.create(Constructor.prototype); // 1. 创建空对象，链接原型
  const result = Constructor.apply(obj, args); // 2. 执行构造函数
  // 3. 返回对象或函数时以返回值优先，否则返回新实例
  return result !== null &&
    (typeof result === "object" || typeof result === "function")
    ? result
    : obj;
}
```

### new 的四步底层拆解

```text theme={null}
第一步：创建一个新的空对象 {}
第二步：将新对象的 __proto__ 指向构造函数的 prototype
第三步：将构造函数的 this 指向新对象并执行
第四步：如果构造函数返回了对象则用该对象，否则返回新对象
```

```javascript theme={null}
// 手写 new 的完整实现
function myNew(Constructor, ...args) {
  // 第一步 + 第二步：创建空对象并链接原型
  const obj = Object.create(Constructor.prototype);
  // 第三步：执行构造函数，this 指向新对象
  const result = Constructor.apply(obj, args);
  // 第四步：构造函数返回对象（含函数）时以它为准，否则返回新实例
  return result !== null &&
    (typeof result === "object" || typeof result === "function")
    ? result
    : obj;
}
```

### new 与 bind 的优先级冲突

当函数被 `bind` 绑定后，再用 `new` 调用，`this` 会指向新实例而非 `bind` 的目标。这是规范明确规定的：`[[Construct]]` 内部方法会强制覆盖之前的 `this` 上下文。

```javascript theme={null}
function Foo(name) {
  this.name = name;
}
const Bound = Foo.bind({ name: "obj" });
const instance = new Bound("newFoo");
console.log(instance.name); // 'newFoo'（不是 'obj'）
```

### 构造函数返回值的特殊行为

如果构造函数返回一个**非 null 的对象**，`new` 表达式的结果就是该对象，而非新创建的实例。这个特性可以用来实现单例模式。

```javascript theme={null}
// 利用 return 对象实现单例
let instance = null;
function Singleton() {
  if (instance) return instance;
  this.name = "singleton";
  instance = this;
}
const a = new Singleton();
const b = new Singleton();
console.log(a === b); // true
```

***

## Q4: ES6 Class 的本质与继承陷阱

ES6 的 `class` 被称为**原型链的语法糖**。但 V8 对 `class` 做了极其严格的底层加固：

* `class` 内部自带严格模式
* 方法默认 `enumerable: false`（不可枚举）
* 必须且只能通过 `new` 调用，直接调用会报错

### class 与 ES5 构造函数的本质区别

| 对比维度 | ES5 构造函数 | ES6 class |
| - | - | - |
| 调用方式 | 可直接调用 `fn()` | 必须 `new`，否则报错 |
| 严格模式 | 需手动声明 | 内部自动开启 |
| 方法枚举 | `enumerable: true` | `enumerable: false` |
| 变量提升 | 函数声明提升 | 暂时性死区（TDZ） |
| 继承原生构造函数 | `Array.call(this)` 不生效 | `extends Array` 完美支持 |

### extends 与 super 做了什么？

继承的核心在于 `super` 关键字的两种形态：

1. **当作函数调用**：`super(name)` 本质上是 `Parent.call(this, name)`
2. **当作对象访问**：`super.method()` 本质上是在父类的 `prototype` 上去查找方法，然后**强行把当前子类的实例作为 `this` 传进去执行**

> [!WARNING]
>
> 在 ES5 中，继承是"先创造子类实例的 `this`，再把父类的属性强加给它"。而在 ES6 Class 中，继承机制完全反转：**子类根本没有自己的 `this`**！它必须先调用 `super()`，由父类完成底层的 `this` 构建和实例化，然后子类才能拿到这个 `this` 进行加工。如果你在 `super()` 之前访问 `this`，引擎会直接抛出 `ReferenceError`。

### new\.target 与原生构造函数继承

`new.target` 是 ES6 引入的隐藏指针，它指向当前被 `new` 的构造函数。子类调用 `super()` 时，`new.target` 会指向子类，导致底层分配的内部槽（Internal Slots）是子类结构而非父类结构。这是 ES5 无法实现原生构造函数继承的根本原因。

```javascript theme={null}
class MyArray extends Array {
  constructor(...args) {
    super(...args);
    // new.target === MyArray，底层分配的内部槽是 MyArray 结构
  }
}
const arr = new MyArray(1, 2, 3);
arr instanceof MyArray; // true
arr instanceof Array; // true
```

***

## 底层源码：V8 中的 this 与 class

this 在 V8 底层究竟是怎么解析的？

`this` 的四种绑定规则并不是引擎里面写了四个 if-else 分支，而是依赖底层对象结构的设计（详见 [V8 源码：src/objects/js-function.h](https://github.com/v8/v8/blob/main/src/objects/js-function.h)）：

* **普通函数**：通过内部方法 `[[Call]]` 执行，它的 `this` 参数是在运行时动态传入的。
* **箭头函数**：在 V8 底层的 `SharedFunctionInfo` 中，其 `[[ThisMode]]` 被死死标记为 `kLexical`。当引擎准备为它解析 `this` 时，一旦发现是 `kLexical`，会**直接跳过运行时动态绑定阶段**，强行去读取闭包上下文 `Context` 中的 `this`。

ES6 Class 到底比 ES5 函数高级在哪里？

V8 专门给 Class 赋予了 `FunctionSyntaxKind::kClassConstructor` 的标记，这会直接改变编译器的内联缓存（Inline Cache）策略。更关键的是，Class 继承原生构造函数（如 `Array`）能正确工作，是因为 ES6 的 `[[Construct]]` 内部机制引入了 `new.target` 这一隐藏指针。子类在调用 `super()` 时，`new.target` 会指向子类，导致底层分配的内部槽（Internal Slots）是完美的子类结构，这是 ES5 的 `Array.call(this)` 做不到的。

***

## 十、及格回答 vs 满分回答

### 问题："说说 JavaScript 中的 this 机制？"

**及格回答（60-70分）：**

> "this 指向调用它的对象。四种绑定规则：默认绑定指向 window，隐式绑定指向调用对象，显式绑定通过 call/apply/bind 强制改 this，new 绑定指向新出来的实例。优先级是 new > bind > 隐式 > 默认。"

问题出在：**死记硬背型选手。** 能背出规则表，但一旦放到复杂的 React 闭包或者箭头函数嵌套里必挂无疑。

**满分回答（90-100分）：**

> "`this` 是执行上下文中的动态指针。其绑定规则遵循 `new > 显式(bind) > 隐式 > 默认` 的优先级。`new` 能覆盖 `bind`，是因为底层 `[[Construct]]` 初始化新对象后，会强制覆盖之前的 `this` 上下文。<sup>\[1]</sup>"

> "箭头函数完全不参与这套动态绑定大战。在 V8 编译期，它就被硬性打上了 `[[ThisMode]] = kLexical` 的标签。引擎甚至不为它分配动态绑定的资源，而是将其视为自由变量，直接沿词法环境去外层抓取，因此任何 `call/bind` 都对其无效。<sup>\[2]</sup>"

> "在 React 类组件回调传参等实际业务中，由于独立调用导致的'隐式绑定丢失'是报错重灾区。通过 `constructor` 中的提前 `bind` 或使用箭头函数属性是标准的工程化解法。ES6 Class 继承原生构造函数（如 `Array`）能正确工作，是因为 `new.target` 隐藏指针保证了子类内部槽的正确分配。<sup>\[3]</sup>"

### 常见追问

> [accordion] 手写一个彻底的 bind, 并兼容 new 调用
>
> **答题要点：**
> 利用闭包返回新函数，检测 new\.target。
>
> **高分回答：**
> "核心逻辑分两步：第一步用 `apply` 绑定上下文。第二步最关键，要在返回的包装函数内部做一个拦截：`if (this instanceof boundFn)`，如果为 `true`，说明它正在被 `new` 关键字作为构造器调用，此时必须无视我们之前传入的上下文 `context`，老老实实把原本构造函数的 `this` 指向当前这个新实例！"

> [accordion] super.method() 里的 this 指向谁?
>
> **答题要点：**
> 搞清方法出处与执行上下文的分离。
>
> **高分回答：**
> "指向的是**子类实例**，绝不是父类实例。`super.method()` 这个语法的底层逻辑是：去父类的 `prototype` 上翻出那个方法，但是**以当前子类实例作为 `this` 来执行它**。所以即使在调用父类方法，如果父类方法里有 `this.name`，它读出来的依然是子类实例上的 `name` 属性。"

> [accordion] call, bind, new 的优先级到底怎么排?
>
> **答题要点：**
> 用代码验证优先级。
>
> **高分回答：**
> "new 的优先级最高。即使函数已经被 `bind` 强行死绑到了某个对象上，一旦遭遇 `new` 关键字，`bind` 指定的 `this` 也会被直接废弃，`this` 被强行指向了新创建的实例。验证代码：
>
> ```javascript theme={null}
> function Foo() {
>   this.name = "Foo";
> }
> const bound = Foo.bind({ name: "obj" });
> const instance = new bound();
> console.log(instance.name); // 'Foo'（不是 'obj'）
> ```
>
> 优先级：new > bind > call/apply > 隐式 > 默认。"

> [accordion] 箭头函数的 this 为什么不能被 call/bind 修改?
>
> **答题要点：**
> 从 V8 底层的 ThisMode 标记解释。
>
> **高分回答：**
> "箭头函数在 V8 编译期就被硬性打上了 `[[ThisMode]] = kLexical` 的标签。引擎甚至不为它分配动态绑定的资源，而是将其视为自由变量，直接沿词法环境去外层抓取。因此 `call`、`apply`、`bind` 的第一个参数会被完全忽略——箭头函数根本不走 `[[Call]]` 的 `thisArgument` 流程。"

> [accordion] 对象方法中的 this 在不同调用方式下如何变化?
>
> **答题要点：**
> 对比方法调用与独立调用。
>
> **高分回答：**
> "`this` 的值完全取决于**调用方式**而非定义位置。同一个方法：
>
> ```javascript theme={null}
> const obj = {
>   name: 'obj',
>   greet() { console.log(this.name); }
> };
> obj.greet();              // 'obj'（隐式绑定）
> const fn = obj.greet;
> fn();                     // undefined（默认绑定，严格模式下 undefined）
> obj.greet.call({ name: 'other' }); // 'other'（显式绑定）
> ```
>
> 核心：把方法赋值给变量后独立调用，会导致隐式绑定丢失，退化为默认绑定。这是最常见的 `this` 相关 bug。"

> [accordion] setTimeout 中的 this 指向谁?
>
> **答题要点：**
> setTimeout 回调是独立调用，this 退化为默认绑定。
>
> **高分回答：**
> "`setTimeout` 的回调函数是独立调用的，因此 `this` 指向 `window`（非严格模式）或 `undefined`（严格模式）。
>
> ```javascript theme={null}
> const obj = {
>   name: 'obj',
>   greet() {
>     setTimeout(function() {
>       console.log(this.name); // undefined（默认绑定）
>     }, 100);
>   }
> };
> ```
>
> 解决方案：使用箭头函数捕获外层 `this`，或在调用前 `bind(this)`。"

> [accordion] class 中的 this 指向和普通函数有什么不同?
>
> **答题要点：**
> class 内部自动严格模式 + 必须 new 调用。
>
> **高分回答：**
> "ES6 class 内部自动开启严格模式，因此直接调用构造函数时 `this` 为 `undefined`（而非 `window`），引擎直接报错。此外，class 中定义的方法在底层依然存在于 `prototype` 上，方法内的 `this` 遵循相同的绑定规则——谁调用指向谁。如果把 class 方法传给 `setTimeout`，`this` 同样会丢失。"

> [accordion] 在 React 中如何彻底解决 this 丢失问题?
>
> **答题要点：**
> 对比四种方案的优劣，推荐最佳实践。
>
> **高分回答：**
> "四种方案：
>
> 1. **constructor 中 bind**：最推荐，只创建一次函数实例，不影响每次 render 的性能。
> 2. **箭头函数类属性**：最简洁，需要 babel 插件支持，底层每次创建新函数引用。
> 3. **render 中 bind**：`<button onClick={this.handleClick.bind(this)}>`，每次 render 创建新函数，影响子组件 diff。
> 4. **render 中箭头函数**：同上，每次 render 创建新函数。
>
> 最佳实践：函数组件 + Hooks 彻底消除了 `this` 的问题，这是 React 团队推荐的方向。"

***

## 延伸阅读与引用参考

* [MDN Web Docs: this](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/this)：*MDN 官方对 this 机制的详尽文档。* <sup>\[1]</sup>
* [V8 Blog: JavaScript ES6 class semantics](https://v8.dev/blog/classes)：*V8 团队解析 ES6 class 在引擎中的实现细节。* <sup>\[2]</sup>
* [V8 Source Code: js-function.h](https://github.com/v8/v8/blob/main/src/objects/js-function.h)：*V8 中函数对象的 C++ 头文件，包含箭头函数的 this 模式定义。* <sup>\[3]</sup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.