美文网首页
前端面试题:MVC、MVP、MVVM的区别?

前端面试题:MVC、MVP、MVVM的区别?

作者: 刷题刷到手抽筋 | 来源:发表于2022-05-21 08:27 被阅读0次

    简介

    MVC,MVP和MVVM都是常见的软件架构设计模式(Architectural Pattern),它通过分离关注点来改进代码的组织方式。不同于设计模式(Design Pattern),只是为了解决一类问题而总结出的抽象方法,一种架构模式往往使用了多种设计模式。
    —— 参考文章【1】

    它们目标都是解耦,解耦好处一个是关注点分离,提升代码可维护和可读性,并且提升代码复用性。

    它们都将应用抽象分离成视图、逻辑、数据3层。

    MVC将应用抽象为数据层(Model)、视图(View)、逻辑(Controller) ,这样数据、视图、逻辑的代码各自汇聚。各层之间的通信模式则没有严格限制,在实际开发中也有各种实现(摘自文章【2】中的示意图),有这样的,

    image.png

    也有这样的

    image.png

    还有这样的

    image.png

    但有一点可以确定的是,在MVC模式中,Model和View可能有耦合,即MVC仅仅将应用抽象,并未限制数据流

    前端面试刷题网站灵题库,收集大厂面试真题,相关知识点详细解析。】

    MVP则在MVC基础上,限定了通信方式,即Model和View之间不直接通信,都通过Presenter通信,这个Presenter和MVC中的Controller一脉相承,代表应用中的逻辑层。Presenter负责项目中的逻辑,并且直接与View和Model通信,操作数据更新更新后手动同步到View上。

    image.png

    MVP模式限制了Model和View之间通信,让Model和View解耦更彻底,代码更容易被复用。

    MVP模式也有问题,它的问题在于Presenter的负担很重,Presenter需要知道View和Model的结构,并且在Model变化时候需要手动操作View,增加编码负担,降低代码维护性。

    于是MVVM设计了VM层,即ViewModel层,ViewModel自动同步数据到视图,用VM代替P之后,MVVM自动从Model映射到View(实现方式是模板渲染),不需要用户手动操作视图,这样代码更简单不易出错,代码更好阅读和维护。

    image.png

    从上面对MVC、MVP、MVVM的描述可以看出,它们是递进关系,不断优化的:MVC中Model和View还有一定程度的耦合,而在MVP和MVVM中View和Model彻底分离,View和Model不知道彼此的存在,View和Model只向外暴露方法让Presenter调用;MVVM通过自动同步数据更新到视图,解决了MVP中手动同步的痛点,简化了代码。

    总结

    如果面试中被问到:MVC、MVP和MVVM之间的区别是什么?可以这么回答:

    MVC将应用抽象为数据层(Model)、视图层(View)、逻辑层(controller),降低了项目耦合。但MVC并未限制数据流,Model和View之间可以通信。

    MVP则限制了Model和View的交互都要通过Presenter,这样对Model和View解耦,提升项目维护性和模块复用性。

    而MVVM是对MVP的P的改造,用VM替换P,将很多手动的数据=>视图的同步操作自动化,降低了代码复杂度,提升可维护性。

    那么什么是MVVM?MVVM是一种软件架构设计模式,它抽离了视图、数据和逻辑,并限定了Model和View只能通过VM进行通信,VM订阅Model并在数据更新时候自动同步到视图。

    示例

    下面我们通过一个简单的例子来展示不同的模式。

    要实现的效果很简单,就是一个开关按钮,点击会切换开关状态状态。

    image.png

    1. 原生写法

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="utf-8" />
        <title>origin</title>
      </head>
      <body>
        <div id="root"></div>
        <script src="origin.js"></script>
      </body>
    </html>
    
    // origin.js
    var isShow = true;
    
    var modal = document.createElement('div');
    var switchButton = document.createElement('button');
    switchButton.innerText = '关';
    switchButton.onclick = function () {
      if (isShow) {
        switchButton.innerText = '开';
        isShow = false;
      }
      else {
        switchButton.innerText = '关';
        isShow = true;
      }
    }
    modal.appendChild(switchButton);
    document.getElementById('root').appendChild(modal);
    

    2. MVC

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8" />
        <title>mvc</title>
    </head>
    <body>
        <div id="root"></div>
        <script src="mvc.js"></script>
    </body>
    </html>
    
    // mvc.js
    class View {
        constructor(controller) {
            this.controller = controller;
        }
        render(model) {
            const button = document.createElement('button');
            const isOpen = model.getState().isOpen;
            button.innerText = isOpen ? '关' : '开';
            button.onclick = this.controller.switch;
    
            const root = document.getElementById('root');
            root.innerHTML = '';
            root.appendChild(button);
            return button;
        }
    };
    
    class Model {
        state = {
            isOpen: false
        };
    
        _views = [];
    
        getState() {
            return this.state;
        }
    
        register(view) {
            this._views.push(view);
        }
    
        switch() {
            this._update({
                isOpen: !this.state.isOpen
            });
        }
    
        _update(data) {
            Object.assign(this.state, data);
            this._notify();
        }
    
        _notify() {
            this._views.forEach(view => view.render(this));
        }
    };
    
    class Controller {
        constructor() {
            this._view = new View(this);
            this._model = new Model();
            this._model.register(this._view);
            this._view.render(this._model);
        }
        switch = () => {
            this._model.switch();
            console.log('switch!!!');
        };
    }
    
    const controller = new Controller();
    

    3. MVP

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="utf-8" />
        <title>mvp</title>
    </head>
    <body>
        <div id="root"></div>
        <script src="mvp.js"></script>
    </body>
    </html>
    
    // mvp.js
    class View {
      constructor(presenter) {
        this.presenter = presenter;
      }
      // view不需要关注model的数据结构,只关注自己需要的数据
      // 这样也不会存在view使用了model的其他数据的情况
      render({isOpen}) {
        const button = document.createElement('button');
        button.innerText = isOpen ? '关' : '开';
        button.onclick = this.presenter.switch;
        
        const root = document.getElementById('root');
        root.innerHTML = '';
        root.appendChild(button);
        return button;
      }
    };
    
    // model不需要在数据更新时候触发视图更新,只负责数据存储
    class Model {
      state = {
        isOpen: false
      };
      
      getState() {
        return this.state;
      }
      
      switch() {
        this.state.isOpen = !this.state.isOpen;
      }
    };
    
    // persenter需要知道m和v的结构,并且要在数据改变时候更新视图,还要处理所有的交互逻辑
    class Presenter {
      constructor() {
        this._view = new View(this);
        this._model = new Model();
        
        const {isOpen} = this._model.getState();
        this._view.render({isOpen});
      }
      switch = () => {
        this._model.switch();
        
        const {isOpen} = this._model.getState();
        this._view.render({isOpen});
        
        console.log('switch!!!');
      };
    }
    
    const presenter = new Presenter();
    

    3. MVVM

    使用vue示例

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
        <title>mvvm</title>
        <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
      </head>
      <body>
        <div id="root"></div>
        <script src="mvvm.js"></script>
      </body>
    </html>
    
    // mvvm.js
    var app = new Vue({
        el: '#root',
        template: `<button @click="onSwitch()">{{isOpen ? '关' : '开'}}</button>`,
        data: {
            isOpen: false
        },
        methods: {
            onSwitch() {
                this.isOpen = !this.isOpen;
            }
        }
    });
    

    参考文章

    MVC、MVP、MVVM的区别和联系(精讲版)【1】

    MVC,MVP 和 MVVM 的图示 - 阮一峰的网络日志【2】

    浅析前端开发中的 MVC/MVP/MVVM 模式【3】

    相关文章

      网友评论

          本文标题:前端面试题:MVC、MVP、MVVM的区别?

          本文链接:https://www.haomeiwen.com/subject/hsmpurtx.html