You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TS/ES6继承子类中箭头函数属性访问为undefined问题

问题背景

定义了基础类MyEndpoint,以及继承自该类的子类MyCustomEndpoint,业务代码中通过new MyCustomEndpoint(router);的方式实例化子类。异常现象为:在子类重写的initMyRoutes方法中,访问handleIsNotOk时得到的值为undefined。

复现代码
import express from 'express';

export class MyEndpoint {
    constructor(router: express.Router) {
        this.initMyRoutes(router); 
    }

    protected initMyRoutes(router: express.Router) {
        router.get(`/v1/isok`, this.handleIsOk);
    }

    protected handleIsOk = (req: express.Request, res: express.Response) => {
        res.send(200);
    }
}


export class MyCustomEndpoint extends MyEndpoint{
    private test = "Test";

    protected initMyRoutes(router: express.Router) {
        console.log(this.handleIsNotOk); // undefined
        console.log(this.handleIsNotOkFn);

        router.get(`/v1/isokcustom`, this.handleIsOk); // 正常工作
        router.get(`/v1/isnotok`,this.handleIsNotOk); // 异常
        router.get(`/v1/isnotokfn`, this.handleIsNotOkFn); // 异常
        router.get(`/v1/isnotokfn`, (req, res) => this.handleIsNotOkFn(req,res)); // 可运行但风格不统一
    }

    protected handleIsNotOk = (req: express.Request, res: express.Response) => {
        res.send(200);
    }

    protected handleIsNotOkFn(req: express.Request, res: express.Response){
        console.log(this.test); // undefined
        res.send(200);
    }
}

// 实例化逻辑
const router = express.Router();
new MyCustomEndpoint(router);
异常表现
  • 子类initMyRoutes方法中同步打印this.handleIsNotOk结果为undefined
  • 直接将this.handleIsNotOk、this.handleIsNotOkFn传入router.get作为路由回调时无法正常工作,其中普通方法handleIsNotOkFn执行时内部访问this.test也为undefined
  • 仅通过箭头函数包裹后调用this.handleIsNotOkFn(req,res)的写法可以正常运行,但该写法和其他路由注册的风格不一致,不够优雅
根本原因

问题由两个JavaScript/TypeScript类的核心机制共同导致:

1. ES6类继承的实例字段初始化时序问题

ES6类继承的实例初始化顺序是严格固定的:

  1. 创建空实例对象,绑定原型链
  2. 初始化父类定义的所有实例字段(包括箭头函数属性、普通实例属性)
  3. 执行父类constructor内的全部代码
  4. 父类构造逻辑完全执行结束后,才会初始化子类定义的所有实例字段(子类中写的private test = "Test"、protected handleIsNotOk = (req,res)=>{}都属于这一阶段才会赋值到实例上)
  5. 若子类自定义了constructor,最后执行子类constructor中super()之后的代码

代码中父类constructor直接调用了this.initMyRoutes(router),由于子类重写了initMyRoutes方法,实际执行的是子类的路由注册逻辑,但此时子类的实例字段还完全没有开始初始化,this.handleIsNotOk、this.test这类子类实例属性根本没被赋值,因此同步打印this.handleIsNotOk会得到undefined。

父类的handleIsOk之所以能正常访问,是因为它是父类的实例字段,在父类constructor执行前就已经完成初始化,所以调用initMyRoutes时可以正常拿到引用。

2. 普通类方法的this绑定丢失

handleIsNotOkFn是定义在类原型上的普通方法,不是实例自身的属性。当直接把this.handleIsNotOkFn作为函数引用传给router.get时,Express后续收到请求触发回调时,只会单独调用这个函数,不会自动把类实例绑定为函数内的this,导致方法内部的this指向丢失(严格模式下为undefined,非严格模式下指向全局对象),自然访问不到实例上的this.test属性。

箭头函数属性本身不会有这个问题:箭头函数没有自己的this,会捕获定义时上下文中的this(也就是类实例),只要箭头函数属性完成初始化,直接把引用传给router.get也不会丢失this指向——但代码中调用initMyRoutes时子类的箭头函数属性还没初始化,所以拿不到引用。

箭头函数包裹写法生效的原因

(req, res) => this.handleIsNotOkFn(req,res)是一个箭头函数,会在定义时捕获类实例的this。路由注册阶段只是把这个箭头函数存到Express的路由表中,不会立刻执行;等后续请求真正进来触发回调时,类实例早就完成了全部初始化流程,此时通过this.handleIsNotOkFn()的形式显式调用方法,this会正确指向类实例,因此可以正常访问所有实例属性。


内容的提问来源于stack exchange,提问作者Zerotwelve

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.02 21:27:34