This article first appeared in nuggets column, published in Liao Keyu’s independent blog, please keep the original link.
MVC, MVP, and MVVM are common Architectural patterns that improve the way code is organized by separating concerns. Unlike Design patterns, which are abstract methods to solve a class of problems, an architectural Pattern often uses multiple Design patterns.
To understand MVC, MVP, and MVVM, it’s important to know the similarities and differences between them. The different parts are C(Controller), P(Presenter), VM(View-model), and the same part is MV(Model-view).
Model&View
Here is a component that can add and subtract values: it displays values, and two buttons can add and subtract values, updating the display.
We will follow this “chestnut” and try to implement a simple Web application with MVC/MVP/MVVM patterns in JavaScript.
Model
The Model layer encapsulates the data associated with the application’s business logic and how it is processed. Here, we encapsulate the numerical variables needed in Model, and define add, sub, and getVal to manipulate the numerical methods.
var myapp = {}; // Create the application object
myapp.Model = function() {
var val = 0; // Data to operate on
/* How to manipulate data */
this.add = function(v) {
if (val < 100) val += v;
};
this.sub = function(v) {
if (val > 0) val -= v;
};
this.getVal = function() {
return val;
};
};Copy the code
View
View, as the View layer, is mainly responsible for the display of data.
myapp.View = function() {
/* View element */
var $num = $('#num'),
$incBtn = $('#increase'),
$decBtn = $('#decrease');
/* Render data */
this.render = function(model) {
$num.text(model.getVal() + 'rmb');
};
};Copy the code
The logic of the data from the model layer to the view layer is now complete through Model&View. But for an application, this is not enough. We need to respond to user actions and update the View and Model synchronously. Therefore, controller is introduced into MVC to define how the user interface responds to user input. It connects the model and view, and is used to control the flow of the application and deal with user behavior and data changes.
MVC
The computer world was chaotic and uncluttered, and then there was a creator who abstracted the real world into a model, separated human-computer interaction from application logic into a View, and then there was air, water, chickens, eggs, and so on. — Front-end MVC Metamorphosis
In the 1970s, Xerox PARC, the company that invented the graphical user interface (GUI), developed the Smalltalk programming language and began writing graphical applications with it.
By the time smalltalk-80 was released, an engineer named Trygve Reenskaug had designed the MVC (Model-View-Controller) architecture for Smalltalk, which greatly reduced the difficulty of managing GUI applications. It is then heavily used to build desktop and server-side applications.
As shown, solid lines represent method calls and dotted lines represent event notifications.
MVC allows you to change the way the View responds to the user input without changing the View. The user’s operation on the View is handed over to the Controller. In the Controller, the interface of the Model is called to operate on the data in response to the events of the View.
Model
The Model layer is used to store the data of the business, and the Model notifies the relevant views when the data changes.
myapp.Model = function() {
var val = 0;
this.add = function(v) {
if (val < 100) val += v;
};
this.sub = function(v) {
if (val > 0) val -= v;
};
this.getVal = function() {
returnval; }; / * Observer mode * /var self = this,
views = [];
this.register = function(view) {
views.push(view);
};
this.notify = function() {
for(var i = 0; i < views.length; i++) { views[i].render(self); }}; };Copy the code
The observer pattern is used between the Model and the View, where the View registers with the Model in advance to observe the Model in order to update the data that changes on the Model.
View
The policy pattern is used between view and Controller, where the View introduces instances of controller to implement specific response policies, such as the button click event in this chestnut:
myapp.View = function(controller) {
var $num = $('#num'),
$incBtn = $('#increase'),
$decBtn = $('#decrease');
this.render = function(model) {
$num.text(model.getVal() + 'rmb');
};
/* Bind event */
$incBtn.click(controller.increase);
$decBtn.click(controller.decrease);
};Copy the code
If you want to implement a different response policy you can simply replace it with a different Controller instance.
Controller
The controller is the link between the model and the view. MVC encapsulates the response mechanism in the Controller object. When the user interacts with your application, the event triggers in the controller start working.
myapp.Controller = function() {
var model = null,
view = null;
this.init = function() {
/* Initialize Model and View */
model = new myapp.Model();
view = new myapp.View(this);
/* View registers with Model and notifies View when Model updates */
model.register(view);
model.notify();
};
/* Let the Model update the value and tell the View to update the View */
this.increase = function() {
model.add(1);
model.notify();
};
this.decrease = function() {
model.sub(1);
model.notify();
};
};Copy the code
Here we instantiate the View and register it with the corresponding Model instance. When the Model changes, we notify the View to update it. Here we use the observer mode.
When we execute the application, we initialize it with Controller:
(function() {
var controller = newmyapp.Controller(); controller.init(); }) ();Copy the code
It is obvious that the business logic of MVC mode is mainly focused on Controller, while the front View has the ability to process user events independently. When each event flows through Controller, this layer will become very bloated. In MVC, View and Controller are generally one-to-one corresponding, which represent a component together. The too close connection between View and Controller makes the reusability of Controller a problem. What should I do if I want to share a Controller with multiple views? Here’s a solution:
Actually, I was going to say MVP mode…
MVP
MVP (Model-View-Presenter) is an improvement of THE MVC pattern, proposed by Taligent, a subsidiary of IBM. Similar to MVC: Controller/Presenter handles business logic, Model handles data, and View handles display.
Although in MVC, a View can access the Model directly, the MVP View does not use the Model directly. Instead, it provides an interface for the Presenter to update the Model and then update the View through observer mode.
Compared with MVC, MVP mode decouples View and Model, completely separating View and Model to make responsibility division clearer. Since the View doesn’t depend on the Model, it can be spun off as a component that provides a set of interfaces to the upper level.
Model
myapp.Model = function() {
var val = 0;
this.add = function(v) {
if (val < 100) val += v;
};
this.sub = function(v) {
if (val > 0) val -= v;
};
this.getVal = function() {
return val;
};
};Copy the code
The Model layer is still the data that is primarily relevant to the business and the corresponding methods of processing that data.
View
myapp.View = function() {
var $num = $('#num'),
$incBtn = $('#increase'),
$decBtn = $('#decrease');
this.render = function(model) {
$num.text(model.getVal() + 'rmb');
};
this.init = function() {
var presenter = new myapp.Presenter(this);
$incBtn.click(presenter.increase);
$decBtn.click(presenter.decrease);
};
};Copy the code
The MVP defines the interface between the Presenter and View, so that user operations on the View are transferred to the Presenter. For example, you can have the View expose a setter interface for the Presenter to call, and when the Presenter notifies the Model of updates, the Presenter calls the interface provided by the View to update the View.
Presenter
myapp.Presenter = function(view) {
var _model = new myapp.Model();
var _view = view;
_view.render(_model);
this.increase = function() {
_model.add(1);
_view.render(_model);
};
this.decrease = function() {
_model.sub(1);
_view.render(_model);
};
};Copy the code
Presenters are the middleman between the View and the Model. In addition to basic business logic, there is a lot of code that needs to “manually synchronize” data from the View to the Model and from the Model to the View, which can be very heavy and difficult to maintain. And because there is no data binding, the Presenter has to focus too much on a particular view if the view needs to be rendered increases, and the Presenter needs to change when the view needs to change.
Run the program with View as the entry:
(function() {
var view = newmyapp.View(); view.init(); }) ();Copy the code
MVVM
MVVM (Model-view-ViewModel) was first proposed by Microsoft. ViewModel means “Model of View” — the Model of a View. This concept was very popular in the front end circles for a while, so much so that many beginners compared jQuery to Vue…
MVVM automates the synchronization logic between View and Model. The View and Model synchronization is no longer handled manually by the Presenter, but by the data binding function provided by the framework, which simply tells it which part of the Model the View displays.
Here we use Vue to complete the chestnut.
Model
In MVVM, we can call the Model the data layer because it only cares about the data itself and doesn’t care about any behavior (formatting the data is the responsibility of the View), and we can think of it here as a JSON-like data object.
var data = {
val: 0
};Copy the code
View
Unlike MVC/MVP, views in MVVM use template syntax to declaratively render data into the DOM, and when the ViewModel updates the Model, it updates it to the View via data binding. It is written as follows:
<div id="myapp">
<div>
<span>{{ val }}rmb</span>
</div>
<div>
<button v-on:click="sub(1)">-</button>
<button v-on:click="add(1)">+</button>
</div>
</div>Copy the code
ViewModel
The ViewModel is basically MVC’s Controller and MVP’s Presenter, which is the focus of the whole pattern. The business logic is also focused here, and one of the core is data binding, which we’ll talk about later. Unlike MVP, without the interface provided by the View to The Presente, the data synchronization between the View and the Model, which was previously the responsibility of the Presenter, is handled by the data binding in the ViewModel. When the Model changes, the ViewModel automatically updates. As the ViewModel changes, the Model updates.
new Vue({
el: '#myapp'.data: data,
methods: {
add(v) {
if(this.val < 100) {
this.val += v;
}
},
sub(v) {
if(this.val > 0) {
this.val -= v; }}}});Copy the code
Overall, it’s much simpler than MVC/MVP. Not only does it simplify business and interface dependencies, but it also solves the problem of frequently updating data (previously, DOM manipulation with jQuery was cumbersome). Because in MVVM, the View is unaware of the Model’s existence, and the ViewModel and Model are unaware of the View, this low-coupling pattern makes the development process easier and improves application reusability.
Data binding
Two-way data binding, which can be simply and inappropriately understood as a template engine, but renders in real time based on data changes. Under the Interface: True MV* Mode
In Vue, two-way data-binding technology is used, that is, the change of View can make the change of Model in real time, and the change of Model can be updated to the View in real time.
“They say it’s patentable.”
Different MVVM frameworks have different techniques for implementing bidirectional data binding. At present, some mainstream front-end frameworks implement data binding in the following ways:
- Data hijacking (Vue)
- Publish/subscribe (Knockout, Backbone)
- Dirty checking (Angular)
We’ll focus on Vue here.
Vue adopts data hijacking & published-subscribe mode. It hijacks (monitors) getters and setters of attributes through object-defineProperty () provided by ES5, notifies subscribers when data (objects) change, and triggers corresponding listening callbacks. And because synchronization is triggered on different data, changes can be precisely sent to the bound view, rather than all data being checked at once. To implement bidirectional data binding in Vue, there are roughly three modules: Observer, Compile and Watcher, as shown in the figure below:
-
The Observer data listener is responsible for listening on all attributes of a data object (data hijacking) and notifying subscribers of changes to the data.
-
The Compiler instruction parser scans the template, parses the instructions, and then binds the specified events.
-
Watcher subscribers associate observers with Compile and can subscribe to receive notification of property changes, perform instruction binding actions, and update views. Update() is a method of its own that executes the callbacks bound in Compile to Update the view.
The data was hijacked
Generally, data hijacking is carried out through object. defineProperty method. The corresponding function in Vue is defineReactive.
var foo = {
name: 'vue'.version: '2.0'
}
function observe(data) {
if(! data ||typeofdata ! = ='object') {
return
}
// Hijack object attributes recursively
Object.keys(data).forEach(function(key) { defineReactive(data, key, data[key]); })}function defineReactive(obj, key, value) {
// Listen for sub-attributes such as 'name' or 'version' in the data object here
observe(value)
Object.defineProperty(obj, key, {
get: function reactiveGetter() {
return value
},
set: function reactiveSetter(newVal) {
if (value === newVal) {
return
} else {
value = newVal
console.log('Listening succeeded:${value} --> ${newVal}`)
}
}
})
}
observe(foo)
foo.name = 'angular' // "Listener succeeded: vue --> angular"Copy the code
This requires implementing a message subscriber Dep. Watcher uses the Dep to add subscribers. When data changes, dep.notify () is triggered. Watcher calls its update() method to update the view.
It’s writing, it’s writing, it’s getting further and further away from the subject… Data hijacking on the first so much it ~ for want to in-depth vue.js students can refer to the hook three four vue.js source code learning notes
conclusion
The purpose of MV* is to decouple the data, business logic and interface of the application and separate concerns, which is not only conducive to team collaboration and testing, but also conducive to maintenance and management. The business logic no longer cares about reading and writing the underlying data, which is presented to the business logic layer as objects. From MVC –> MVP –> MVVM, just like a process of beating strange upgrades, they are based on MVC with the development of The Times and application environment evolved.
Before we agonize over what architectural patterns or frameworks to use, we should understand them first. Take a moment to think about the business scenario and development requirements, and find the best solution for each requirement. We use this framework because we believe in its ideas and believe that it can improve development efficiency and solve current problems, not just because everyone is learning it.
There are people who love new technology, and there are people who don’t. As Dickens wrote in A Tale of Two Cities:
It was the best of times, it was the worst of times, it was the age of wisdom, it was the age of foolishness; It was the season of faith, it was the season of incredulity; It was the season of light, it was the season of darkness; It was the spring of hope, it was the winter of despair. They have everything before them, they have nothing before them; People are going straight to heaven; People are going straight to hell.
Please keep an open mind to change and do not be blind or conservative in the face of new technology.
I wrote for two days and consulted a lot of materials. It was also a learning process for me. I hope it will be helpful to the students after reading this article. I would appreciate your advice if I have any deficiencies.
Some reference resources: GUI Architectures Under the Interface: Restoring the Real MV* Model Front-end MVC Distortion Notes In-depth understanding of the JavaScript series 250 lines to implement a simple MVVM