前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
首页程序员面试题库class 方法 this 绑定与 bind
JavaScript函数与作用域

class 里的方法传出去当回调,this 为什么就丢了?

class 方法和普通对象方法一样,this 都看调用时点号前面是谁。区别是 class 体永远是严格模式,方法单独传出去调用时 this 是 undefined,一读属性就报 TypeError。所以要在构造器里 bind,或者改成箭头函数字段。

前端进阶之旅 · 一题精讲更新于 2026.09.29
JavaScript#函数与作用域
先看核心答案读代码示例
理解线索

方法放在哪里,决定 `this` 稳不稳

  1. 原型方法所有实例共用一个函数,this 每次调用时才确定
  2. 构造器 bind在实例上放一个绑定好的版本,遮住原型上的同名方法
  3. 箭头函数字段实例初始化时创建,this 固定是这个实例

箭头字段是实例自己的属性,子类用普通方法覆写同名方法会被它挡住,子类的版本根本调不到。

核心回答

先记住这个答案

class 方法定义在原型上,this 的规则和普通函数一样,谁调用就指向谁,class 并不会帮你自动绑定实例。counter.increment() 能拿到实例,是因为调用时点号前面是 counter。一旦把 counter.increment 当回调传给 addEventListener、setTimeout 或者解构出来,调用时就没有点号前面的对象了。和普通对象方法的区别在于,class 体内永远是严格模式,这时 this 是 undefined,读属性直接抛 TypeError;普通对象方法在非严格模式下 this 会退回全局对象,不报错但悄悄算错。常见修法是在构造器里 this.increment = this.increment.bind(this),或者写成箭头函数字段。

  • class 方法不会自动绑定实例,this 看调用方式
  • class 体是严格模式,丢了 this 就是 undefined
  • 构造器 bind 和箭头字段都会给每个实例单独建函数
  • 要解绑的回调必须存同一个函数引用

同一个方法,class 和普通对象丢 this 的表现不同

先看一段代码,把方法从对象上拿下来单独调用。

把方法拿出来单独调用JavaScript
class Counter {
  count = 0
  increment() {
    this.count++
    return this.count
  }
}

const counter = new Counter()
console.log(counter.increment()) // 通过实例调用,this 是 counter

const fn = counter.increment // 只拿到函数本身
try {
  fn()
} catch (err) {
  console.log(err.name) // class 体是严格模式,this 是 undefined
}

const plain = { count: 0, increment() { this.count++; return this.count } }
const g = plain.increment
console.log(g()) // 非严格模式下 this 退回全局对象
查看输出与解释
1
TypeError
NaN

第一次调用正常返回 1。fn() 调用时没有接收者,class 里的代码是严格模式,this 就是 undefined,this.count 直接抛错。普通对象的方法在非严格脚本里单独调用,this 变成全局对象,全局上没有 count,undefined 加 1 得到 NaN,还顺手往全局上写了一个 count 属性。 所以两者的绑定规则其实一样,差别只在严格模式。class 的报错反而是好事,问题当场暴露,不会带着错误数据跑下去。

构造器 bind 和箭头字段怎么选

两种修法都能让方法传出去之后 this 还指向实例,但它们放函数的位置不一样,影响内存、继承和测试。

对比两种写法放在哪里JavaScript
class Button {
  label = 'ok'
  constructor() {
    this.onBind = this.onBind.bind(this) // 实例上放一个绑定版本
  }
  onBind() { return this.label }
  onArrow = () => this.label // 字段初始化时创建,this 固定为实例
}

const a = new Button()
const b = new Button()
console.log(a.onArrow === b.onArrow, a.onBind === b.onBind)
console.log('onArrow' in Button.prototype, 'onBind' in Button.prototype)

const { onBind, onArrow } = a // 解构出来单独调用
console.log(onBind(), onArrow())
查看输出与解释
false false
false true
ok ok

两种写法下,每个实例都有自己的一份函数,所以 a 和 b 的回调不相等。区别在于 bind 写法原型上还保留着原始的 onBind,子类可以正常覆写,测试时也能在原型上打桩;箭头字段只存在于实例上,原型里根本没有它。

实例多的时候要不要在意
每个实例多一个函数,几百几千个组件实例基本感觉不到。真正要在意的是列表里成千上万个对象都带着回调,这种情况更好的做法是事件委托,只在父节点挂一个监听器,而不是纠结 bind 还是箭头。
回答前,多想一步

容易答错的地方

以为 class 会自动绑定 this
class 只做了两件事:方法放到原型上,方法体进入严格模式。this 仍然看调用方式。React 以前的 class 组件要在构造器里 bind,就是因为 JSX 里写 onClick={this.handleClick} 传出去的是裸函数。
注册和解绑时各写一次 .bind(this)
bind 每次都返回一个新函数。addEventListener('click', this.handle.bind(this)) 之后再用 removeEventListener('click', this.handle.bind(this)),两次拿到的是两个不同的函数,监听器根本移不掉。要在构造器里绑定一次存起来,注册和解绑都用它。
以为箭头字段在原型上
箭头函数字段是实例属性,每次 new 都会执行一遍字段初始化。它不在 prototype 上,所以 Button.prototype.onArrow 是 undefined,想用 super.onArrow() 调父类的版本也调不到。
试着用自己的话回答

面试官还会怎么问?

子类用普通方法覆写父类的箭头字段,会生效吗?

不会。父类构造时先把箭头函数作为实例自己的属性放上去,子类的普通方法在 Sub.prototype 上,属性查找先命中实例属性,子类的版本永远被挡住。父类用箭头字段、子类又想覆写,就要改成父类用原型方法加构造器 bind。

构造器里 this.handle.bind(this),子类覆写了 handle 会绑到哪个?

绑到子类的版本。执行父类构造器时,实例的原型已经是子类的 prototype,this.handle 顺着原型链先找到子类的方法,再把它绑定到实例上。

除了这两种,还有别的办法保住 this 吗?

可以在传回调的地方包一层:button.addEventListener('click', () => counter.increment())。它不改类的定义,但每次渲染都会新建函数,而且没有保存引用就没法解绑,适合一次性、不需要移除的场景。

从一道题,走向一组知识

把知识连起来

函数与作用域

var、let、const 的作用域有什么不同?

class 声明和 let 一样有暂时性死区,声明之前使用同样会抛 ReferenceError。

函数与作用域

生成器里的 yield* 是干什么的?和 yield 有什么区别?

class 里可以写 *[Symbol.iterator]() 这样的生成器方法,里面的 this 同样由调用方式决定。

参考资料

  • JavaScript:函数与作用域参考资料

示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。

本题目录
  1. 先记住这个答案
  2. 同一个方法,class 和普通对象丢 this 的表现不同
  3. 构造器 bind 和箭头字段怎么选
  4. 容易答错的地方
  5. 面试官还会怎么问
  6. 把知识连起来
读懂,再试着讲出来

先看核心答案,再读代码。最后展开追问,检查自己有没有遗漏边界。

试着回答追问
浏览全部面试题理解原理,也关注真实的使用场景。回到顶部 ↑