MVC 架构

View

V:View 用于展示内容和捕获用户动作和输入。

Model

M:管理业务逻辑和数据,关注数据如何存储和产生。

Translator

在 View 和 Model 中建立关联,使得视图层和模型能够不直接关联。

MVC

用户上传了头像

  1. 用户选择了新图片
  2. View 将这次行为发送给 Controller
  3. Controller 更新了 Model
  4. Controller 告诉 View 更新显示

MVC 流程简单直接,但是随着应用程序的增长,Controller 会变得臃肿。

MVP

MVP 引入了一个职责更突出的 Presenter (主持人/协调器),由它来处理 UI 逻辑。

MVVM

MVVM 引入了 View-Model 和 View 之间的数据绑定,它支持数据变量在两个方向上的自动传播。

MVVM 减少了显示更新逻辑,ViewModel 负责同步原始模型数据,以自动反映用户界面的变化。

MVVMC

VIPER

例子

MVC 代码

<!DOCTYPE html>
<html>
<head>
    <title>MVC 示例</title>
    <style>
        .container { margin: 20px; }
        .user-info { border: 1px solid #ccc; padding: 10px; margin-top: 10px; }
        button { margin-top: 10px; padding: 5px 10px; }
    </style>
</head>
<body>
    <div class="container">
        <h2>MVC 架构示例</h2>
        <button id="loadUserBtn">加载用户信息</button>
        <div id="userContainer" class="user-info"></div>
        <div id="errorMsg" style="color: red;"></div>
    </div>

    <script>
        // Model:管理数据和业务逻辑
        const UserModel = {
            userData: null,
            // 模拟获取用户数据
            fetchUser: function() {
                return new Promise((resolve, reject) => {
                    // 模拟API请求延迟
                    setTimeout(() => {
                        const mockData = { id: 1, name: "张三", age: 30 };
                        this.userData = mockData;
                        resolve(mockData);
                    }, 800);
                });
            },
            getUser: function() {
                return this.userData;
            }
        };

        // View:负责UI展示,可直接访问Model
        const UserView = {
            // 初始化视图
            init: function() {
                this.userContainer = document.getElementById('userContainer');
                this.errorMsg = document.getElementById('errorMsg');
                this.loadButton = document.getElementById('loadUserBtn');
            },
            // 展示用户信息
            renderUser: function() {
                // 直接访问Model获取数据 - MVC的特点
                const user = UserModel.getUser();
                if (user) {
                    this.userContainer.innerHTML = `
                        <p>ID: ${user.id}</p>
                        <p>姓名: ${user.name}</p>
                        <p>年龄: ${user.age}</p>
                    `;
                    this.errorMsg.textContent = '';
                }
            },
            // 展示错误信息
            showError: function(message) {
                this.errorMsg.textContent = message;
                this.userContainer.innerHTML = '';
            },
            // 绑定事件到Controller
            bindLoadUser: function(handler) {
                this.loadButton.addEventListener('click', handler);
            }
        };

        // Controller:协调Model和View
        const UserController = {
            // 初始化
            init: function() {
                UserView.init();
                // 绑定视图事件到控制器方法
                UserView.bindLoadUser(this.handleLoadUser.bind(this));
            },
            // 处理加载用户事件
            handleLoadUser: function() {
                UserView.loadButton.disabled = true;
                UserView.userContainer.innerHTML = "加载中...";
                
                // 调用Model获取数据
                UserModel.fetchUser()
                    .then(() => {
                        // 通知View渲染 - 也可由View直接监听Model变化
                        UserView.renderUser();
                        UserView.loadButton.disabled = false;
                    })
                    .catch((error) => {
                        UserView.showError(error.message);
                        UserView.loadButton.disabled = false;
                    });
            }
        };

        // 启动应用
        UserController.init();
    </script>
</body>
</html>
    

MVP 代码

<!DOCTYPE html>
<html>
<head>
    <title>MVP 示例</title>
    <style>
        .container { margin: 20px; }
        .user-info { border: 1px solid #ccc; padding: 10px; margin-top: 10px; }
        button { margin-top: 10px; padding: 5px 10px; }
    </style>
</head>
<body>
    <div class="container">
        <h2>MVP 架构示例</h2>
        <button id="loadUserBtn">加载用户信息</button>
        <div id="userContainer" class="user-info"></div>
        <div id="errorMsg" style="color: red;"></div>
    </div>

    <script>
        // Model:管理数据和业务逻辑(与MVC相同)
        const UserModel = {
            userData: null,
            fetchUser: function() {
                return new Promise((resolve, reject) => {
                    setTimeout(() => {
                        const mockData = { id: 1, name: "张三", age: 30 };
                        this.userData = mockData;
                        resolve(mockData);
                    }, 800);
                });
            },
            getUser: function() {
                return this.userData;
            }
        };

        // View:仅负责UI展示,不访问Model,通过接口与Presenter交互
        const UserView = {
            // 初始化视图
            init: function(presenter) {
                this.presenter = presenter;
                this.userContainer = document.getElementById('userContainer');
                this.errorMsg = document.getElementById('errorMsg');
                this.loadButton = document.getElementById('loadUserBtn');
                this.bindEvents();
            },
            // 绑定事件,直接委托给Presenter处理
            bindEvents: function() {
                this.loadButton.addEventListener('click', () => {
                    this.presenter.onLoadUserClicked();
                });
            },
            // 展示加载状态
            showLoading: function() {
                this.userContainer.innerHTML = "加载中...";
                this.loadButton.disabled = true;
                this.errorMsg.textContent = '';
            },
            // 展示用户信息(由Presenter调用)
            displayUser: function(user) {
                this.userContainer.innerHTML = `
                    <p>ID: ${user.id}</p>
                    <p>姓名: ${user.name}</p>
                    <p>年龄: ${user.age}</p>
                `;
                this.loadButton.disabled = false;
            },
            // 展示错误信息(由Presenter调用)
            displayError: function(message) {
                this.errorMsg.textContent = message;
                this.userContainer.innerHTML = '';
                this.loadButton.disabled = false;
            }
        };

        // Presenter:作为中间层,协调View和Model
        const UserPresenter = {
            // 初始化
            init: function() {
                // Presenter持有View和Model的引用
                this.model = UserModel;
                UserView.init(this); // 将自己传递给View
            },
            // 处理加载用户点击事件
            onLoadUserClicked: function() {
                // 通知View显示加载状态
                UserView.showLoading();
                
                // 调用Model获取数据
                this.model.fetchUser()
                    .then((user) => {
                        // 获取数据后,通知View展示
                        UserView.displayUser(user);
                    })
                    .catch((error) => {
                        // 出错时,通知View展示错误
                        UserView.displayError(error.message || "加载失败");
                    });
            }
        };

        // 启动应用
        UserPresenter.init();
    </script>
</body>
</html>
    

MVVM 代码

<!DOCTYPE html>
<html>
<head>
    <title>MVVM 示例</title>
    <style>
        .container { margin: 20px; }
        .user-info { border: 1px solid #ccc; padding: 10px; margin-top: 10px; }
        button { margin-top: 10px; padding: 5px 10px; cursor: pointer; }
        .error { color: red; }
        .loading { color: #666; }
    </style>
</head>
<body>
    <div class="container">
        <h2>MVVM 架构示例(用户信息展示)</h2>
        <!-- View:仅负责 UI 结构,通过“数据绑定”关联 ViewModel -->
        <button id="loadUserBtn" :disabled="isLoading">加载用户信息</button>
        <div class="user-info" v-if="hasUser">
            <p>ID: {{ user.id }}</p>
            <p>姓名: {{ user.name }}</p>
            <p>年龄: {{ user.age }}</p>
            <!-- 双向绑定示例:修改年龄后自动同步到 ViewModel -->
            <p>修改年龄: <input type="number" v-model="user.age"></p>
        </div>
        <div class="loading" v-if="isLoading">加载中...</div>
        <div class="error" v-if="hasError">{{ errorMsg }}</div>
    </div>

    <script>
        // 1. Model:管理原始数据和业务逻辑(与 MVC/MVP 一致,不依赖其他层)
        const UserModel = {
            userData: null, // 存储原始用户数据
            error: null,    // 存储错误信息

            // 模拟从后端获取用户数据(异步操作)
            fetchUser: function () {
                return new Promise((resolve, reject) => {
                    setTimeout(() => {
                        try {
                            // 模拟后端返回的原始数据
                            const mockRawData = { id: 1, name: "张三", age: 30 };
                            this.userData = mockRawData; // 存储原始数据
                            this.error = null;
                            resolve(mockRawData); // 成功时返回数据
                        } catch (err) {
                            this.error = "数据加载失败";
                            reject(new Error(this.error)); // 失败时返回错误
                        }
                    }, 800); // 模拟网络延迟
                });
            },

            // 更新用户年龄(业务逻辑:年龄不能小于 0)
            updateUserAge: function (newAge) {
                if (newAge < 0) {
                    this.error = "年龄不能为负数";
                    return false;
                }
                this.userData.age = newAge;
                this.error = null;
                return true;
            },

            // 获取当前用户数据
            getUser: function () {
                return this.userData;
            },

            // 获取错误信息
            getError: function () {
                return this.error;
            }
        };

        // 2. ViewModel:核心中间层,实现数据绑定和逻辑处理,不依赖 View 具体实现
        const UserViewModel = {
            // 可观察数据(供 View 绑定,数据变化时自动通知 View 更新)
            state: {
                isLoading: false,  // 加载状态
                hasUser: false,    // 是否有用户数据
                hasError: false,   // 是否有错误
                errorMsg: "",      // 错误信息
                user: { id: "", name: "", age: 0 } // 供 View 绑定的用户数据(已转换为 UI 友好格式)
            },

            // 初始化:关联 Model,监听数据变化
            init: function () {
                this.model = UserModel;
                // 绑定 View 事件(通过 DOM 事件委托,避免 ViewModel 直接操作 View)
                this.bindViewEvents();
            },

            // 绑定 View 事件(如按钮点击)
            bindViewEvents: function () {
                const loadBtn = document.getElementById("loadUserBtn");
                loadBtn.addEventListener("click", () => this.loadUser());
            },

            // 加载用户数据(核心逻辑,调用 Model 并更新 ViewModel 状态)
            loadUser: function () {
                // 1. 更新状态:显示加载中
                this.setState({
                    isLoading: true,
                    hasUser: false,
                    hasError: false
                });

                // 2. 调用 Model 获取数据
                this.model.fetchUser()
                    .then((rawUser) => {
                        // 3. 数据转换(Model 原始数据 → View 可展示数据,此处简单映射,复杂场景可做格式处理)
                        const uiUser = {
                            id: rawUser.id,
                            name: rawUser.name,
                            age: rawUser.age
                        };

                        // 4. 更新状态:显示用户数据
                        this.setState({
                            isLoading: false,
                            hasUser: true,
                            user: uiUser
                        });

                        // 5. 监听用户年龄修改(双向绑定核心:View 输入 → ViewModel → Model)
                        this.watchUserAgeChange();
                    })
                    .catch((err) => {
                        // 6. 更新状态:显示错误
                        this.setState({
                            isLoading: false,
                            hasError: true,
                            errorMsg: this.model.getError()
                        });
                    });
            },

            // 监听年龄输入变化(双向绑定:View 输入同步到 Model)
            watchUserAgeChange: function () {
                const ageInput = document.querySelector('input[type="number"]');
                ageInput.addEventListener("input", (e) => {
                    const newAge = parseInt(e.target.value) || 0;
                    // 1. 调用 Model 更新业务数据
                    const updateSuccess = this.model.updateUserAge(newAge);
                    // 2. 同步更新 ViewModel 状态(错误信息或年龄)
                    if (updateSuccess) {
                        this.setState({
                            user: { ...this.state.user, age: newAge },
                            hasError: false,
                            errorMsg: ""
                        });
                    } else {
                        this.setState({
                            hasError: true,
                            errorMsg: this.model.getError()
                        });
                    }
                });
            },

            // 更新 ViewModel 状态(触发 View 重新渲染)
            setState: function (newState) {
                // 合并新状态到当前状态
                Object.assign(this.state, newState);
                // 通知 View 更新(核心:状态变化 → View 自动刷新)
                this.updateView();
            },

            // 更新 View(根据 ViewModel 状态渲染 UI,模拟框架的“数据驱动视图”)
            updateView: function () {
                const { isLoading, hasUser, hasError, errorMsg, user } = this.state;

                // 控制“加载中”显示/隐藏
                document.querySelector(".loading").style.display = isLoading ? "block" : "none";
                // 控制“用户信息”显示/隐藏
                document.querySelector(".user-info").style.display = hasUser ? "block" : "none";
                // 控制“错误信息”显示/隐藏
                document.querySelector(".error").style.display = hasError ? "block" : "none";
                // 渲染错误信息
                document.querySelector(".error").textContent = errorMsg;
                // 渲染用户数据(若有数据)
                if (hasUser) {
                    document.querySelector(".user-info p:nth-child(1)").textContent = `ID: ${user.id}`;
                    document.querySelector(".user-info p:nth-child(2)").textContent = `姓名: ${user.name}`;
                    document.querySelector(".user-info p:nth-child(3)").textContent = `年龄: ${user.age}`;
                    // 同步年龄输入框的值(双向绑定的“View 同步”)
                    document.querySelector('input[type="number"]').value = user.age;
                }
                // 控制按钮禁用状态
                document.getElementById("loadUserBtn").disabled = isLoading;
            }
        };

        // 3. 启动应用:仅初始化 ViewModel,View 由 ViewModel 自动渲染
        UserViewModel.init();
    </script>
</body>
</html>