What is MVVM?
MVVM actually stands for model-view-viewModel
- Model: The Model layer, which processes the business logic and interacts with the server side
- View: The View layer is responsible for turning the data model into a UI for presentation, which can be easily understood as HTML pages
- ViewModel: The ViewModel layer that connects the Model to the View and acts as a communication bridge between the Model and View
In the MVVM architecture, the View layer and the Model layer are not directly connected, but interact through the ViewModel layer. The ViewModel layer connects the View layer to the Model layer through two-way data binding, making the synchronization between the View layer and the Model layer completely automatic. So developers can only focus on the business logic and don’t need to manually manipulate the DOM.
Vue and MVVM
In fact, the Vue framework is a typical MVVM model of the framework. The Vue framework acts as the ViewModel layer in the MVVM schema.
Use code to understand the relationship:
Use jQuery to manipulate DOM elements, add a button button, and bind the Click event
If (Btn) {let Btn = $(' < button > me < / butten > ') Btn) on (' click ', function () {the console. The log (' point to me... ')}); $('#app').append(btn) }Copy the code
As you can see from the above code, the HTML code responsible for the view is coupled with the JS code responsible for the business logic, which is a serious problem. If we manipulate DOM elements directly, we can cause performance problems, etc
With Vue, you can effectively separate the view layer from the model layer
</button> </div> </template> <script> export default {name:'App', Methods :{handleClick:function(){console.log('... '); } }, } </script>Copy the code
As you can see from the above code, the HTML code responsible for the view is effectively separated from the JS code responsible for the business logic. This is achieved mainly through the Vue framework.
As shown in figure:
Principle of MVVM
As mentioned above, the View layer and the Model layer do not interact directly, but rather interact through the ViewModel layer. The ViewModel layer connects the View layer to the Model layer through two-way data binding, making the synchronization between the View layer and the Model layer completely automatic.
There are several ways to implement data binding:
- Dirty checking (angular.js)
- Data hijacking (vue.js)
- Publisher – subscriber mode (backbone.js)
Angular. js uses dirty value detection to determine whether to update a view if the data has changed. The simplest way is to use setInterval() polling to detect data changes. Angular only checks for dirty values when a specified event is triggered, roughly as follows:
- DOM events, such as the user entering text, clicking a button, and so on. (
ng-click
) - XHR response event (
$http
) - Browser Location change event (
$location
) - The Timer event (
$timeout
,$interval
) - perform
$digest()
或$apply()
For data hijacking and the publisher-subscriber model, please refer to my article: Vue Data Hijacking, Observer VS Publish/subscribe
Implement two-way data binding steps
To implement MVVM bidirectional binding, the following must be implemented:
1. Compile an instruction parser, scan and parse the instructions of each element node, replace data according to the instruction template, and bind the corresponding update function
2. Implement a data listener Observer, which can listen to all the attributes of the data object. If there is any change, it can get the latest value and notify the subscriber (Dep).
3. Implement a Watcher that acts as a bridge between the Observer and Compile, subscribing to and receiving notification of each property change, and executing the corresponding callback function (publish) of the instruction binding to update the view
4.MVVM entry function, integration of the above three
Flow chart:
Implement MVVM
If you want to see the full implementation process and want to write it by hand, please go to: Vue MVVM implementation process case Study
<! DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Two-way data-binding</title> </head> <body> <div id="app"> <input type="text" v-model="text"> {{ text }} </div> <script> function observe (obj, vm) { Object.keys(obj).forEach(function (key) { defineReactive(vm, key, obj[key]); }); } function defineReactive (obj, key, val) { var dep = new Dep(); Object.defineProperty(obj, key, { get: function () { if (Dep.target) dep.addSub(Dep.target); return val }, set: function (newVal) { if (newVal === val) return val = newVal; dep.notify(); }}); } function nodeToFragment (node, vm) { var flag = document.createDocumentFragment(); var child; while (child = node.firstChild) { compile(child, vm); flag.appendChild(child); } return flag; } function compile (node, vm) { var reg = /\{\{(.*)\}\}/; If (node.nodeType === = 1) {var attr = Node.attributes; For (var I = 0; i < attr.length; i++) { if (attr[i].nodeName == 'v-model') { var name = attr[i].nodeValue; Node.addeventlistener ('input', function (e) {// Assign the corresponding data attribute, Then the set method vm[name] = e.target.value is triggered. }); node.value = vm[name]; // Assign the value of data to the node node.removeAttribute('v-model'); } } new Watcher(vm, node, name, 'input'); } // Text if (node.nodeType === 3) {if (reg.test(node.nodevalue)) {var name = RegExp.$1; // Get the matching string name = name.trim(); new Watcher(vm, node, name, 'text'); }} function Watcher (vm, node, name, nodeType) {// this is the Watcher function dep. target = this; // console.log(this); this.name = name; this.node = node; this.vm = vm; this.nodeType = nodeType; this.update(); Dep.target = null; } Watcher.prototype = { update: function () { this.get(); if (this.nodeType == 'text') { this.node.nodeValue = this.value; } if (this.nodeType == 'input') { this.node.value = this.value; Get: function () {this.value = this.vm[this.name]; Function Dep () {this.subs = []} dep. prototype = {addSub: function(sub) {this.push (sub); }, notify: function() { this.subs.forEach(function(sub) { sub.update(); }); }}; function Vue (options) { this.data = options.data; var data = this.data; observe(data, this); var id = options.el; var dom = nodeToFragment(document.getElementById(id), this); Document.getelementbyid (id).appendChild(dom); } var vm = new Vue({ el: 'app', data: { text: 'hello world' } }); </script> </body> </html>Copy the code