本系列其他译文请看[JS工作机制 - 小白1991的专栏 - 掘金 (juejin.cn)] (juejin.cn/column/6988…
本文阅读指数:3
MVC模式在前端已经不如以前火了.React的Flux框架,Vue的MVVM框架是大家讨论最多的。但是这两个框架都脱胎于MVC。本文MVC的实现和原则,推荐阅读一下。
编程中,我们需要从用户接口层去拆分应用逻辑。这种拆分能让我们的前端和后端工程师同时在一个工程上工作而互不影响。同时也让我们的代码干净,可复用。
MV架构的目标,就是允许开发者去写模块化和可复用的代码。这一章中,我们会讨论什么是MV架构,为什么开发者需要它,JS中如何构建MV,以及实现MV的不同方式。
什么是MVC
MVC 就是 Model, View, 和 Controller.这是一种软件架构,将程序逻辑分割成三个互相关联的组件。它分离了数据的收集和数据的展示。
JS实现MVC不仅是模块化,而且因为Model返回的值是没有固定格式的,所以它可以被不同的接口使用,大大提升了可复用性。(说实话这种方式并不推荐)
JS 中MVC如何构建
这一节,我们讨论一下
Model
Model组件管理应用的数据。这是MVC中的核心部分,它从Controller接受指令,执行用户的输入。 这个组件直接从Controller拿指令,而补依赖用户接口。 用户输入的数据,被发送到Model.Model进行管理,然后发送到View
View
View组件负责最终显示给用户的信息。这个组件接受用户输入,同时从Controller/Model获取数据并显示给用户。
Controller
Controller是Model和View的连接器。它是应用的大脑,负责把View 的输入转换成命令。这些命令决定了 View的显示的信息和Model的处理方式。
我们来使用JS构建一个MVC应用。
我们使用MongoDB作为数据库, Node.js/Express 的EJS模板当作Controller。
首先,我们创建一个Node.js/Express 应用,入口是index.js
const express = require ('express');
const cors = require ('cors');
const bodyParser = require ('body-parser');
const Usercontroller = require('./controllers/Usercontroller');
var app = express();
app.get("/", (req, res) => {
res.render("home");
});
app.get("/login", (req, res) => {
res.render("login");
});
app.get("/register", (req, res) => {
res.render("register");
});
const { mong } = require('./db.js');
app.set("view engine", "ejs");
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cors());
app.use('/uploads', express.static('uploads'));
app.listen(3000, () => console.log('server started successfully at port : 3000'));
app.use('/', Usercontroller);
接下来,我们创建Model,接受我们的发送的数据。我们使用Mongoose来创建数据模式来定义我们的数据在数据库中的结构。我们创建一个文件夹,叫models
。在我们的模型文件中,我们创建一个User.js
文件。
文件中包含了给用户使用的数据模式。
/*Using an ORM (mongoose) to interact with a simple user database model*/
const mongoose = require('mongoose');
var User = mongoose.model('User', {
username: { type: String, required: true },
password: { type: String, required: true },
first_name: { type: String },
last_name: { type: String},
});
//export database model(user)
module.exports = { User };
接着,我们创建一个文件在工程的根目录下,叫db.js
。这个文件将会负责应用和MongoDB连接。
const mongoose = require("mongoose");
mongoose.connect(
"mongodb://localhost:27017/Crypto",
{ useNewUrlParser: true, useUnifiedTopology: true },
(err) => {
if (!err) console.log("mongodb connected successfully");
else
console.log(
"Error in DB connection:" + JSON.stringify(err, undefined, 2)
);
}
);
现在,创建一个Controller。创建一个Controller位于Model和View中间,包含了我们应用的逻辑。我们在根目录创建一个文件夹controllers
,在controllers
目录下,我们创建一个文件Usercontroller.js
//import all dependencies required
const express = require('express');
const cors = require('cors');
//set variable users as expressRouter
var users = express.Router();
//import user model
var { User } = require('../models/User');
//protect route with cors
users.use(cors());
// Handling user signup
users.post('/register', (req, res) => {
const userData = {
//values should be those in the user model important
username : req.body.username,
password: req.body.password,
first_name: req.body.first_name,
last_name: req.body.last_name,
}
User.findOne({
//ensure username is unique, i.e the username is not already in the database
username: req.body.username
})
.then(user => {
//if the username is unique go ahead and create userData
if (!user) {
User.create(userData)
.then(user => {
//after successfully creating userData display registered message
res.redirect('/login')
})
.catch(err => {
//if an error occured while trying to create userData, go ahead and display the error
res.send('error:' + err)
})
} else {
//if the username is not unique, display that username is already registered with an account
res.json({ error: 'The username ' + req.body.username + ' is registered with an account' })
}
})
.catch(err => {
//display error if an error occured
res.send('error:' + err)
})
})
/*Set route for logging in registered users*/
users.post('/login', (req, res) => {
User.findOne({
//check to see if a username like this is in the database
username: req.body.username,
password: req.body.password
})
.then(user => {
//if the username exist in database then the user exists
if (user) {
const payload = {
username: user.username,
password: user.password,
}
//after successful login display token and payload data
res.redirect('/');
} else {
//if user cannot be found, display the message below
res.json({ error: 'user not found' })
}
})
//catch and display any error that occurs while trying to login user
.catch(err => {
res.send('error:' + err)
})
})
//export routes usercontroller
module.exports = users;
Now, we’ll be creating our View. We’ll create a folder named Views
. In our Views
folder, we’ll create three files for our different Views. The files will be named home.ejs
, login.ejs
and register.ejs
. In your home.ejs
, put in the code below:
接着,创建View。创建一个目录Views
,然后在目录里创建三个文件home.ejs
, login.ejs
和 register.ejs
。
先看home.ejs
:
This is home page
再写一个登录模板 login.ejs
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="//maxcdn.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" rel="stylesheet" id="bootstrap-css">
<script src="//maxcdn.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.1.0/css/all.css"
integrity="sha384-lKuwvrZot6UHsBSfcMvOkWwlCMgc0TaWr+30HWe3a4ltaBwTZhyTEggF5tJv8tbt" crossorigin="anonymous">
<!------ Include the above in your HEAD tag ---------->
<title>Document</title>
</head>
<body>
<div class="container">
<h2 class="text-center">Login Form</h2>
<div class="row justify-content-center">
<div class="col-12 col-md-8 col-lg-6 pb-5">
<!--Form with header-->
<form action="/login" method="POST">
<div class="card border-primary rounded-0">
<div class="card-header p-0">
<div class="bg-info text-white text-center py-2">
<h3><i class="fa fa-envelope"></i> Node.js Crypto</h3>
<p class="m-0">Log into your account</p>
</div>
</div>
<div class="card-body p-3">
<!--Body-->
<div class="form-group">
<div class="input-group mb-2">
<div class="input-group-prepend">
<div class="input-group-text"><i class="fa fa-user text-info"></i></div>
</div>
<input type="text" class="form-control" name="username" placeholder="username">
</div>
</div>
<div class="form-group">
<div class="input-group py-2">
<div class="input-group-prepend">
<div class="input-group-text"><i class="fa fa-envelope text-info"></i></div>
</div>
<input type="text" class="form-control" name="password" placeholder="password">
</div>
</div>
<div class="text-center">
<input type="submit" value="Login" class="btn btn-info btn-block rounded-0 py-2">
<p>No account yet? <a href="/register">Register</a></p>
</div>
</div>
</div>
</form>
<!--Form with header-->
</div>
</div>
最后,写 register.ejs
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="//maxcdn.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" rel="stylesheet" id="bootstrap-css">
<script src="//maxcdn.bootstrapcdn.com/bootstrap/4.1.1/js/bootstrap.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.1.0/css/all.css"
integrity="sha384-lKuwvrZot6UHsBSfcMvOkWwlCMgc0TaWr+30HWe3a4ltaBwTZhyTEggF5tJv8tbt" crossorigin="anonymous">
<!------ Include the above in your HEAD tag ---------->
<title>Document</title>
</head>
<body>
<div class="container">
<h2 class="text-center">Registration Form</h2>
<div class="row justify-content-center">
<div class="col-12 col-md-8 col-lg-6 pb-5">
<!--Form with header-->
<form action="/register" method="POST">
<div class="card border-primary rounded-0">
<div class="card-header p-0">
<div class="bg-info text-white text-center py-2">
<h3><i class="fa fa-envelope"></i> Node.js Crypto</h3>
<p class="m-0">Register an Account</p>
</div>
</div>
<div class="card-body p-3">
<!--Body-->
<div class="form-group">
<div class="input-group mb-2">
<div class="input-group-prepend">
<div class="input-group-text"><i class="fa fa-user text-info"></i></div>
</div>
<input type="text" class="form-control" name="first_name" placeholder="first name">
</div>
</div>
<div class="form-group">
<div class="input-group mb-2">
<div class="input-group-prepend">
<div class="input-group-text"><i class="fa fa-user text-info"></i></div>
</div>
<input type="text" class="form-control" name="last_name" placeholder="last name">
</div>
</div>
<div class="form-group">
<div class="input-group mb-2">
<div class="input-group-prepend">
<div class="input-group-text"><i class="fa fa-envelope text-info"></i></div>
</div>
<input type="text" class="form-control" name="username" placeholder="username">
</div>
</div>
<div class="form-group">
<div class="input-group mb-2">
<div class="input-group-prepend">
<div class="input-group-text"><i class="fa fa-envelope text-info"></i></div>
</div>
<input type="text" class="form-control" name="password" placeholder="password">
</div>
</div>
<div class="text-center">
<input type="submit" value="Register" class="btn btn-info btn-block rounded-0 py-2">
<p>Already have an account? <a href="/login">Login</a></p>
</div>
</div>
</div>
</form>
<!--Form with header-->
</div>
</div>
</div>
</body>
</html>
给应用添加一些样式,style.css
body {
background-color: #2ecc71;
font-family: source-sans-pro, sans-serif;
}
h1 {
margin-left: auto;
margin-top: 50px;
text-align: center;
font-weight: 100;
font-size: 2.8em;
color: #ffffff;
}
div {
width: 500px;
margin: auto;
}
.formStyle {
background-color: #2ecc71;
padding: 20px;
width: 400px;
margin-bottom: 20px;
border-bottom-width: 1px;
border-bottom-style: solid;
border-bottom-color: #ecf0f1;
border-top-style: none;
border-right-style: none;
border-left-style: none;
font-size: 1em;
font-weight: 100;
color: #ffffff;
}
.formButton {
float: right;
background-color:#ffffff;
display:inline-block;
color:#2ecc71;
font-size:28px;
font-weight: 500;
padding:6px 24px;
margin-top: 15px;
margin-right: 60px;
text-decoration:none;
}
.formButton:hover {
background-color: #27ae60;
color:#ffffff;
}
.formButton:active {
position:relative;
top:3px;
}
/*To remove the outline that appears on clicking the input textbox*/
input:focus {
outline: none;
}
/* To format the placeholder text color */
::-webkit-input-placeholder {
color: #ecf0f1;
}
:-moz-placeholder { /* Firefox 18- */
color: #ecf0f1;
}
::-moz-placeholder { /* Firefox 19+ */
color: #ecf0f1;
}
:-ms-input-placeholder {
color: #ecf0f1;
}
万事俱备,开始启动:
node index.js
上面的例子中,我们把应用解耦到Views, Models, 和 Controllers。把Views中的业务逻辑剥离出来。
MVC的不同模式
MVC结构有一些不同的模式。我们看看有哪些,以及怎么在JS中使用。
Hierarchical Model View Controller (HMVC)
在应用中有时候需要提交和恢复评论。例如,一个博客需要定位访客的问题或贺词,所以可能会使用一些内容结构的外挂程序---可以理解为插件。普通的MVC架构没有地方接入这些程序。HMVC允许你创建一些部件,可以放置在不同页面中,而不用在每一个页面去创建一个MVC。例如,一个在线购物平台,你需要在主页,产品页添加一个'添加到购物车'的按钮,我们用部件的形式实现。无论在哪个页面,一旦用户点击了按钮,商品将会有序添加到购物车。这种方式,我们就不用在每一个页面创建一个新的MVC和按钮。这么做会有点复杂。 HMVC让代码模块化和复用性提高。其中HM来显示内容。
在JS中,有一些包可以帮助我们实现HMVC。例如, HMVC帮我们创建模块化程序。上面的例子告诉我们如何使用HMVC来阐明用户的在线购物平台的购物车。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<!--Add to cart button widget can be added anywhere in your page-->
<img src="https://i.ibb.co/9wjtQgr/syed-hussaini-4w1-s-JP9g4-I-unsplash.jpg" alt="Trulli" width="400" height="400">
<button onclick="banana()">Add To Cart</button>
<img src="https://i.ibb.co/FYyBHTg/shelley-pauls-I58f47-LRQYM-unsplash.jpg"
alt="shelley-pauls-I58f47-LRQYM-unsplash" width="400" height="400"></a>
<button onclick="apple()">Add To Cart</button>
<img src="https://i.ibb.co/hBgwB0v/vino-li-OKT6-Ce9fwq-I-unsplash.jpg"
alt="vino-li-OKT6-Ce9fwq-I-unsplash" width="400" height="400"></a>
<button onclick="pineapple()">Add To Cart</button>
<script>
// class Model, which is the Application's Model component
class Model {
constructor(name, price) {
this.name = name;
this.price = price;
}
getPrice() {
return this.price;
}
getName() {
return this.name;
}
}
// class View, which is the Application's View component
class View {
constructor() {
this.controller = null;
}
registerWith(controller) {
this.controller = controller;
this.controller.addView(this);
}
cartItem(name, price) {
console.log(`Name: ${name}\nPrice: ${price}`);
}
addToCart(name, price) {
this.controller.addToCart(name, price);
}
}
// class Controller, which is the Application's Controller component
class Controller {
constructor() {
this.model = null;
this.view = null;
this.cartList = [];
}
addView(view) {
this.view = view;
}
addModel(model) {
this.model = model;
}
updateCart() {
console.log("List of Cart Items:");
for (let i in this.cartList)
this.view.cartItem(
this.cartList[i].getName(),
this.cartList[i].getPrice()
);
console.log("\n");
}
addToCart(name, price) {
this.cartList.push(new Model(name, price, this.cartList.length));
this.updateCart();
}
}
var view = new View();
var controller = new Controller();
view.registerWith(controller);
//Add widgets in any page of your choice
function apple() {
console.log("Adding Apples to cart...");
view.addToCart("Apples", "$40");
}
function banana() {
console.log("Adding Bananas to cart...");
view.addToCart("Banana", "$55");
}
function pineapple() {
console.log("Adding Pineapples to cart...");
view.addToCart("Pineapple", "$50");
}
</script>
</body>
</html>
例子中,开发者增加了一个部件,允许用户在任意界面添加商品到购物车。商品根据它被添加的顺序反射在购物页面(例子中是网页控制台)。HMVC是MVC的一个简单拓展,它多了一个Controller。同理,你还可以添加其他的Controller去删除或者更新购物车。
Model View Adapter (MVA)
普通的MVC模式,数据从Controller到Model。但是MVA让Controller成为中介。因此,Model和View必须流经Controller。这样View和Model就无法直接通信了。这个Controllers也被称之为Adapters
这种模式看起来有点严格,但是很容易调试。这种MVC的特点是,它可以拥有多个Adapters。 我们看一个简单的例子
// class Model, which is the Application's Model component
class Model {
constructor(text) {
this.text = text;
}
// update data in model as text processed by adapter
setText(text) {
this.text = text;
}
// get data from adapter
getText() {
return this.text;
}
}
// class View, which is the Application's View component
class View {
constructor() {
this.adapter = null;
}
registerWith(adapter) {
this.adapter = adapter;
}
// display text entered by user in upper case
changeText(text) {
this.adapter.changeText(text);
}
capitalizeText(text) {
this.adapter.capitalizeText(text);
}
// display upper case text entered by user
displayMessage(text) {
console.log("The text is: " + text);
}
}
// class Adapter, which is the Application's Adapter component.
// We have two adapters, one which changes the text and the other that capitalizes text
class Adapter {
constructor(view) {
this.view = view;
this.model = null;
}
//set value in the model to new text entered by user
setModel(model) {
this.model = model;
}
//get value entered by user
getView() {
return this.view;
}
//change text entered by user to lower case
changeText(text) {
let lower = text.toLowerCase();
this.model.setText(lower);
this.view.displayMessage(this.model.getText());
}
//change text entered by user to upper case
capitalizeText(text) {
let upper = text.toUpperCase();
this.model.setText(upper);
this.view.displayMessage(this.model.getText());
}
}
var model = new Model("Hello world!");
var view = new View();
var adapter = new Adapter(view);
adapter.setModel(model);
view.registerWith(adapter);
adapter.getView().changeText("unoo");
adapter.getView().capitalizeText("HELLO");
这个例子中我们有两个Adapters。一个把字符串转换为大写,另一个则把字符串转换为小写。
Model View Presenter (MVP)
MVP主要用来构建用户接口。这种模式下,Controller被Presenter替代。Presenter处理应用中所有的逻辑。数据被发送到Model,然后在发送到View之前被发送到Presenter
这种模式有点像MVA,因为View和Model不能直接通信。必须要经过Presenter
使用MVP,你可以创建不同的Views,它们使用共一个Persenter但是表现却不同。这样的话我们就可以使用相同的功能区创建不同的用户接口。这也是为什么用户接口的设计者喜欢这种模式。
看一个简单的例子
// class Model, which is the Application's Model component
class Model {
constructor(text) {
this.text = text;
}
// update data in model as text processed by presenter
setText(text) {
this.text = text;
}
// get data from presenter
getText() {
return this.text;
}
}
// class View, which is the Application's View component
class View {
constructor() {
this.presenter = null;
}
registerWith(presenter) {
this.presenter = presenter;
}
// if user tries to change text to lower case, display error.
displayError() {
console.log("Text is not in upper case");
}
// display upper case text entered by user
displayMessage(text) {
console.log("The text is: " + text);
}
changeText(text) {
this.presenter.changeText(text);
}
}
// class Presenter, which is the Application's Presenter component
class Presenter {
constructor(view) {
this.view = view;
this.model = null;
}
//set value in the model to new text entered by user
setModel(model) {
this.model = model;
}
//get value entered by user
getView() {
return this.view;
}
//if the text entered by user is in capital letter, let the view display text. Else, let view display an error.
changeText(text) {
if (text !== text.toUpperCase()) {
this.view.displayError();
} else {
this.model.setText(text);
this.view.displayMessage(this.model.getText());
}
}
}
var model = new Model("Hello world!");
var view = new View();
var presenter = new Presenter(view);
presenter.setModel(model);
view.registerWith(presenter);
presenter.getView().changeText("this will throw an error");
presenter.getView().changeText("ADMIN");
From the example above, the class
model
contains our data which is the text to be displayed. While the Presenter
which connects the Model to the View initializes the Model and returns the value in the View
. So, if a user tries to change the initial text in the View
, the Presenter
updates the Model
which returns the updated data to be displayed by the View
.
Model View ViewModel (MVVM)
MVVM 从业务逻辑中分离了用户接口。它是基于MVC和MVP的。
这个模式中,Model存储应用需要的数据和信息。ViewModel类似Controller,用来维持Model和View的通信。当用户发送数据,以及需要转换Model中的数据时,它来更新Model。
这种模式允许开发者同时构建。跟MVC和MVP的主要区别是,它提供了一个binder。这个binder让开发者不用再写模板逻辑,他可以自动同步ViewModel和View. 下面的例子展示了MVVM架构把用户输入转换成大写,以及把用户输入内容显示到另一个输入框的过程
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<!--Class View for Application's View-->
Input Text: <input type="text" name = "name" id="name"> <input type="text" name = "name" id="nameCopy"><br></br>
<script>
// Class Model for Application's Model.
class Model {
constructor() {
// Make initial text in field box "Input Something"
this.model = { name: "Input Something" };
this.observers = [];
}
subscribe(observer) {
this.observers.push(observer);
}
// notify observers once there's a change of text in input field
notifyObservers(attrName, newVal) {
for (var i = 0; i < this.observers.length; i++) {
this.observers[i](attrName, newVal);
}
}
// get the current text entered by user.
getCurrentText(nameKey) {
return this.model[nameKey];
}
setTextValue(nameKey, value) {
this.model[nameKey] = value;
this.notifyObservers(nameKey, value);
}
}
// Class ViewModel for Application's ViewModel.
class ViewModel {
constructor(model) {
//bind the View element to Model element.
this.bind = function (viewElement, modelElement) {
// get the current text entered by user to Model by listening on View element's value
viewElement.value = model.getCurrentText(modelElement);
model.subscribe(function (attrName, newValue) {
document.getElementsByName(attrName).forEach(function (elem) {
elem.value = newValue.toUpperCase();
});
});
viewElement.addEventListener("input", function () {
model.setTextValue(viewElement.name, viewElement.value);
});
};
}
}
var nameInput = document.getElementById("name");
var nameCopy = document.getElementById("nameCopy");
var viewModel = new ViewModel();
var model = new Model();
var viewModel = new ViewModel(model);
viewModel.bind(nameInput, "name");
viewModel.bind(nameCopy, "name");
</script>
</body>
</html>
上面的例子,我们Model是Model
类,包含文本Input Something
。在用户选择输入之前,它是输入框里的内部文本。Model还有一个observer
,监听Model的一切变化。ViewModel从Model中获取当前状态,一旦他们发生了改变,就会更新View。我们的View是HTML代码,包含两个输入框。一个输入框允许用户输入,另一个显示前面输入框的内容。
*使用MVC的JS框架
JS框架是现代前端开发的重要部分。他们组合了一些工具,库和预制代码,帮助我们快速构建web应用。
有很多JS框架,比如Vue.js, Angular, ember.js 等等。
Vue.js
Vue.js使用的是MVVM架构。View和ViewModel进行了双向绑定
var vm = new Vue({ /* options */ })
Viewvm.$el
绑定到Model vm.$data
从上面的图中,我们可以看到用户输入的内容通过ViewModel传递到Model,然后又通过ViewModel返回到View。 DOM 监听处理View的输入到DOM,同时指令处理Model到View
Angular
Angular中的MVC非常直接。主要依赖HTML和JS。Controllers用来收集用户的输入事件,处理他们,并发送到Model。数据从Model发送到View。 和VUE一样,Angular也使用了指令
Ember.js
Ember.js中的绑定,可以开发者创建MVC模式。Handlebars作为视图,Controller是应用逻辑。用户的输入/事件从View传递到Controller,Controller处理之后发送到Model. Ember.js’s MVC 有点像Rails,但是有所不同。Rails是后端框架,接受HTTP请求的。而Ember.js是客户端,收集用户输入和事件。
JS应用中的MVC架构
开发者都想创建合适用户的产品。在构建过程中,开发者要保持模块化和整洁度。例如,他们需要确保代码的可读性,并分布到不同的模块中。开发者不能为了模块化而牺牲代码的可读性。因为不同的开发者也需要在一个项目中合作,最好每一个开发者都知道发生了什么。同时,开发者也不能因为可读性牺牲掉模块化。因为大的代码块运行的时间太久。所以,开发者要综合考虑。
开发者使用MVC可以做到兼顾。在Web应用中,开发者使用瘦客户端模式,让数据从应用的客户端发送到服务端。 服务端负责管理和处理数据。 一个例子是 MEAN 或 MEVN栈。客户端发来的数据(Angular or Vue)被发送到 Node.js/Express.js 服务端,服务端处理数据并发送到数据库(MongoDB)。如果数据不适合被发送到数据库,就会处理它然后发送到View. 客户端作为view,接受用户的输入数据,并且通过数据绑定把服务端的数据渲染出来。 Node.js/Express.js 扮演了Controller,在这里写应用逻辑。它把数据发送到MongoDB,也返回给View.