You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

JavaScript类继承报错:Cannot read property 'click' of undefined 排查

问题分析与解决方案

老哥,我瞅了下你的代码和报错信息,问题其实出在构造函数执行顺序继承逻辑误用这两个地方,咱们一步步拆解:

1. 报错的直接原因:DOM元素未初始化就绑定事件

你看到的Cannot read property 'click' of undefined,核心问题是子类的DOM元素还没赋值,就触发了事件绑定

  • 当你new Display()时,子类构造函数第一行是super(names),这会优先执行父类SaveInput的构造函数
  • 父类构造函数里直接调用了this.events(),此时子类的this.h1 = $('h1')还没执行(因为super之后才会跑子类自己的构造代码)
  • 所以子类的events()方法里访问this.h1时,它还是undefined,调用.click()自然就报错了

2. 更深层的问题:没必要用继承,组合才是正确姿势

你本来只是想获取SaveInput里的names数组,但继承是用来表示**"is-a"(是一种)的关系(比如Dog继承Animal,因为狗是动物的一种)。而你的DisplaySaveInput协作关系**:一个负责收集输入,一个负责操作展示,用继承反而把逻辑搅乱了,还容易出这种顺序问题。


解决方案

方案一:修正继承逻辑(不推荐,但能解决报错)

如果非要用继承,得调整构造函数的执行顺序,让父类的事件绑定不要提前触发:

修改SaveInput类,去掉构造函数里的自动事件调用,改用手动初始化:

import $ from 'jquery';
class SaveInput {
    constructor(){
        this.mainText = $('.main-text');
        this.names = [];
        // 移除自动调用events(),让子类控制时机
    }

    // 父类自己的事件绑定
    bindInputEvents() {
        this.mainText.blur(this.saveNameIndex.bind(this));
    }

    saveNameIndex() {
        this.names = this.mainText.val().split('\n');
        return this.names;
    }
}
export default SaveInput;

修改Display类,先初始化自己的DOM元素,再绑定所有事件:

import $ from 'jquery';
import SaveInput from './SaveInput';
class Display extends SaveInput {
    constructor(){
        super(); 
        // 先初始化子类自己的DOM元素
        this.h1= $('h1');
        // 再分别绑定父类和子类的事件
        this.bindInputEvents();
        this.bindDisplayEvents();
    }

    // 子类自己的事件绑定
    bindDisplayEvents(){
        this.h1.click(this.log.bind(this));
    }

    log () {
        console.log(this.names); // 现在可以正常访问父类的names数组了
    }
}
export default Display;

方案二:用组合代替继承(强烈推荐,逻辑更清晰)

这是更符合面向对象设计的方式,把SaveInput作为Display的一个成员实例,避免继承带来的耦合:

保持SaveInput类不变(或加一个获取names的封装方法):

import $ from 'jquery';
class SaveInput {
    constructor(){
        this.mainText = $('.main-text');
        this.names = [];
        this.events();
    }

    events() {
        this.mainText.blur(this.saveNameIndex.bind(this));
    }

    saveNameIndex() {
        this.names = this.mainText.val().split('\n');
        return this.names;
    }

    // 封装获取names的方法,更符合封装原则
    getNames() {
        return this.names;
    }
}
export default SaveInput;

重写Display类,通过实例化SaveInput来获取数据:

import $ from 'jquery';
import SaveInput from './SaveInput';
class Display {
    constructor(){
        // 组合SaveInput实例,而非继承
        this.inputHandler = new SaveInput();
        this.h1= $('h1');
        this.events();
    }

    events(){
        this.h1.click(this.log.bind(this));
    }

    log () {
        // 通过实例获取names数组
        console.log(this.inputHandler.getNames());
        // 也可以直接访问this.inputHandler.names,不过用get方法更规范
    }
}
export default Display;

额外提一句:你父类构造函数没有接收参数,所以super(names)其实是无效的,传过去的参数不会被处理,这也是个小细节问题。

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

火山引擎 最新活动