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 代码

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
<!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 代码

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
<!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 代码

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
<!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>