MVVM
, short for Model-View-ViewModel
, is an improvement based on the MVC
and MVP
architecture patterns. In MVVM
, the status and behavior of the View
in the MVC
pattern are abstracted, separating the visual UI
from the business logic, more clearly distinguishing the development of the user interface UI
from the development of the business logic and behavior in the application. The MVP
pattern does not belong to the category of the generally defined 23
design patterns, but is usually considered a broadly architectural design pattern.
MVVM
simplifies the dependency between the interface and the business, helping to separate the development of the graphical user interface from the development of business logic or data models. In MVVM
, the ViewModel
acts as a binder to link the view layer UI
with the data layer Model
. When the Model
is updated, the ViewModel
updates the data to the View
through the binder. When the View
triggers a command, it passes the message to the Model
through the ViewModel
. The ViewModel
acts like a black box, in the development process, only needing to focus on presenting the view layer of UI
and the data layer of the abstract model Model
, without needing to pay too much attention to how the ViewModel
passes data and messages. The MVVM
pattern behaves similarly to the MVP
pattern, but its main difference is that it usually adopts two-way binding data-binding
, automating the synchronization logic between the View
and Model
. Previously, the Presenter
was responsible for manually synchronizing the View
and Model
, but now this is handled by the framework's data binding functionality, only needing to inform it which part of the Model
corresponds to the data displayed by the View
.
View <- ViewModel <-> Model
- It results from abstracting things in an object-oriented manner and represents the real content state, being the domain model.
- The
Model
can also be referred to as the data layer, focusing solely on the data itself, without concern for any behavior.
- The
View
is the structure, layout, and appearance that the user sees on the screen, i.e., the visualUI
. - When the
Model
is updated, theViewModel
updates theView
through data binding.
- The
ViewModel
abstracts the view by exposing public properties and commands. - The binder in the
ViewModel
communicates between the view and the data binder. - When the
Model
is updated, theViewModel
updates the data to theView
, and when theView
triggers a command, theViewModel
passes the message to theModel
.
- Loose coupling: The
View
can change independently of theModel
, aViewModel
can be bound to differentView
s, so when theView
changes, theModel
can remain unchanged, and when theModel
changes, theView
can also remain unchanged. - Reusability: Some view logic can be placed in a
ViewModel
, allowing manyView
s to reuse this view logic. - Independent development: Developers can focus on the development of business logic and data in the
Model
, while designers can focus on page design. - Testability: The interface is usually difficult to test, but behavior can be tested through the
ViewModel
.
- For large projects, data binding may require more memory.
- Data binding makes
Bugs
difficult to debug. When the interface is abnormal, it may be due to a problem in theView
code, or it may be due to a problem in theModel
code. Data binding makes it possible for aBug
in one place to be quickly transmitted to another place, making it less easy to locate the original problematic area.
Below is a simple data binding example implemented according to Vue
. However, it is worth noting that Vue
does not fully adhere to the MVVM
model, but rather takes inspiration from it. See https://cn.vuejs.org/v2/guide/instance.html
for more information. To understand why Evan You says Vue
does not fully comply with MVVM
, you can refer to this https://www.zhihu.com/question/327050991
.
<!DOCTYPE html>
<html>
<head>
<title>Data Binding</title>
</head>
<body>
<div id="app">
<div>{{msg}}</div>
<div>{{date}}</div>
<button onclick="update()">Update</button>
</div>
</body>
<script type="text/javascript">
///////////////////////////////////////////////////////////////////////////////
var Mvvm = function(config) {
this.$el = config.el;
this.__root = document.querySelector(this.$el);
this.__originHTML = this.__root.innerHTML;
function __dep(){
this.subscribers = [];
this.addSub = function(watcher){
if(__dep.target && !this.subscribers.includes(__dep.target) ) this.subscribers.push(watcher);
}
this.notifyAll = function(){
this.subscribers.forEach( watcher => watcher.update());
}
}
function __observe(obj){
for(let item in obj){
let dep = new __dep();
let value = obj[item];
if (Object.prototype.toString.call(value) === "[object Object]") __observe(value);
Object.defineProperty(obj, item, {
configurable: true,
enumerable: true,
get: function reactiveGetter() {
if(__dep.target) dep.addSub(__dep.target);
return value;
},
set: function reactiveSetter(newVal) {
if (value === newVal) return value;
value = newVal;
dep.notifyAll();
}
});
}
return obj;
}
this.$data = __observe(config.data);
function __proxy (target) {
for(let item in target){
Object.defineProperty(this, item, {
configurable: true,
enumerable: true,
get: function proxyGetter() {
return this.$data[item];
},
set: function proxySetter(newVal) {
this.$data[item] = newVal;
}
});
}
}
__proxy.call(this, config.data);
function __watcher(fn){
this.update = function(){
fn();
}
this.activeRun = function(){
__dep.target = this;
fn();
__dep.target = null;
}
this.activeRun();
}
new __watcher(() => {
console.log(this.msg, this.date);
})
new __watcher(() => {
var html = String(this.__originHTML||'').replace(/"/g,'\\"').replace(/\s+|\r|\t|\n/g, ' ')
.replace(/\{\{(.)*?\}\}/g, function(value){
return value.replace("{{",'"+(').replace("}}",')+"');
})
html = `var targetHTML = "${html}";return targetHTML;`;
var parsedHTML = new Function(...Object.keys(this.$data), html)(...Object.values(this.$data));
this.__root.innerHTML = parsedHTML;
})
}
///////////////////////////////////////////////////////////////////////////////
var vm = new Mvvm({
el: "#app",
data: {
msg: "1",
date: new Date(),
obj: {
a: 1,
b: 11
}
}
})
function update(){
vm.msg = "updated";
}
///////////////////////////////////////////////////////////////////////////////
</script>
</html>
https://github.com/WindrunnerMax/EveryDay
https://zhuanlan.zhihu.com/p/140071889
https://juejin.cn/post/6844903480126078989
http://www.ruanyifeng.com/blog/2015/02/mvcmvp_mvvm.html