MVC 架构
View
V:View 用于展示内容和捕获用户动作和输入。
Model
M:管理业务逻辑和数据,关注数据如何存储和产生。
Translator
在 View 和 Model 中建立关联,使得视图层和模型能够不直接关联。
MVC
用户上传了头像
- 用户选择了新图片
- View 将这次行为发送给 Controller
- Controller 更新了 Model
- 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>