MVP模式中View和Presenter怎么解耦才不会互相引用?

UE丶光远 阅读 37

我在用原生JS写一个简单的MVP架构,但发现View里要调用Presenter的方法,Presenter又要操作View的DOM,结果两边互相持有对方引用,感觉耦合太紧了。

比如我的View初始化时会this.presenter = new Presenter(this),而Presenter里又存了view实例去更新UI。这样真的符合MVP吗?有没有更干净的解耦方式?

我试过用事件监听,但逻辑一复杂就乱了。现在代码大概是这样:

class View {
  constructor() {
    this.presenter = new Presenter(this);
    this.button = document.getElementById('btn');
    this.button.addEventListener('click', () => {
      this.presenter.handleButtonClick();
    });
  }

  updateText(text) {
    this.button.textContent = text;
  }
}

class Presenter {
  constructor(view) {
    this.view = view;
  }

  handleButtonClick() {
    this.view.updateText('Clicked!');
  }
}
我来解答 赞 3 收藏
二维码
手机扫码查看
1 条解答
Des.耘郗
你这种互相引用的方式确实有点问题,虽然能跑但耦合太重。我给你个更干净的方案,用事件总线来解耦,效率更高而且符合MVP原则。

核心思路是把双向引用变成单向数据流:View只触发事件,Presenter监听事件并处理业务逻辑,然后通过事件通知View更新。这样两边都不会直接引用对方。

// 简单实现事件总线
const EventBus = {
events: {},
emit(event, data) {
if (!this.events[event]) return;
this.events[event].forEach(cb => cb(data));
},
on(event, callback) {
if (!this.events[event]) this.events[event] = [];
this.events[event].push(callback);
}
};

class View {
constructor() {
this.button = document.getElementById('btn');
this.button.addEventListener('click', () => {
EventBus.emit('buttonClicked');
});

EventBus.on('updateText', text => {
this.button.textContent = text;
});
}
}

class Presenter {
constructor() {
EventBus.on('buttonClicked', () => {
this.handleButtonClick();
});
}

handleButtonClick() {
EventBus.emit('updateText', 'Clicked!');
}
}

// 初始化
new View();
new Presenter();


几点关键改进:
1. View和Presenter完全不知道对方的存在,只和事件总线交互
2. View负责渲染和事件触发,Presenter负责业务逻辑
3. 数据流动方向清晰:View -> Event -> Presenter -> Event -> View

比起你的原始方案,这种实现更符合MVP的核心思想,而且扩展性更好。如果后面要加新功能,只需要增加新的事件类型就行,不用改现有代码。
点赞
2026-03-09 01:02