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,因为狗是动物的一种)。而你的Display和SaveInput是协作关系**:一个负责收集输入,一个负责操作展示,用继承反而把逻辑搅乱了,还容易出这种顺序问题。
解决方案
方案一:修正继承逻辑(不推荐,但能解决报错)
如果非要用继承,得调整构造函数的执行顺序,让父类的事件绑定不要提前触发:
修改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




