MVC

MVC is a design pattern that divides an application into three parts: the data (model), the presentation layer (view), and the user interaction layer. Take a look at the diagram below to understand the relationship between the three.

  1. User and application interaction
  2. The controller event handler is fired
  3. The controller requests data from the model and gives it to the view
  4. The view presents data to the user

Model: Used to hold all the data objects of the application. Models don’t have to know the details of views and controllers; they just need to contain data and the logic that directly relates to that data. Any event-handling code, view templates, and model-independent logic should be isolated from the model. View: The view layer is presented to the user and the user interacts with it. In javaScript applications, views are mostly composed of HTML, CSS, and javaScript templates. The view should not contain any logic beyond the simple conditional statements in the template. In fact, like the model, the view should decouple the controller from the rest of the application: the controller is the link between the model and the view. The controller takes events and inputs from the view, processes them, and updates the view accordingly. When the page loads, the controller adds event listeners to the view, such as listening for form submissions and button clicks. Then when the user interacts with the application, the event trigger in the controller comes into play. For example, the early framework backbone of JavaScript framework adopts MVC mode.

The above example seems to be too empty. Here is an example from life: The controller creates a new chat model. 4. Then the controller updates the view. 5.

Model

In MVC, M stands for Model, where logic related to data manipulation and behavior should be placed. For example, if we create a Model object, all operations on the data should be placed in this namespace. Here’s some simplified code, starting with creating a new model and instance

var Model = {
	create: function() {
		this.records = {}
		var object = Object.create(this)
		object.prototype = Object.create(this.prototype)
		return object
	}
}
Copy the code

Create is used to create an object that is modeled after a Model, and then some functions that involve data manipulation including lookup, storage

Var Model = {/*-- code fragment --*/ find: function () {return this.records[this.id]}, save: function () { this.records[this.id] = this } }Copy the code

Here we can use the Model:

user = Model.create()
user.id = 1
user.save()
asset = Model.create()
asset.id = 2
asset.save()
Model.find(1)
=> {id:1}
Copy the code

And you can see we’ve already found this object. The model is the data part and we’re done.

Control

So let’s talk about controllers in MVC. When the page is loaded, the controller binds the event handler to the view and handles the appropriate callbacks and necessary interfacing with the model. Here is a simple example of a controller:

var ToggleView = {
	init: function (view) {
		this.view = $(view)
		this.view.mouseover(this.toggleClass, true)
		this.view.mouseout(this.toggleClass, false)
	},
	this.toggleClass: function () {
		this.view.toggleClass('over', e.data)
	}
}
Copy the code

This gives us simple control over a view, hover over the element to add over class, remove and remove over class. Then add some simple styles for example

Ex:.over {color: red} p{color: black} so the controller is connected to the view. One of the features in MVC is that one controller controls one view, and as the size of the project increases, you need a state machine to manage those controllers. Var StateMachine = function() {} SateMachine. Add = function(controller) {this.bind('change', function(e, current) { if (controller == current) { controller.activate() } else { controller.deactivate() } }) controller.active = Function () {this.trigger('change', controller)}} var con1 = {activate: funtion() { $('#con1').addClass('active') }, deactivate: function () { $('#con1').removeClass('active') } } var con2 = { activate: funtion() { $('#con2').addClass('active') }, deactivate: The function () {$(' # con2). RemoveClass (' active ')}} / / create a state machine, Var sm = new StateMachine sm.add(con1) sm.add(con2)Copy the code

This gives you simple controller management, and finally we’re adding some CSS styles.

#con1, #con2 { display: none }
#con2.active, #con2.active { display: block }
Copy the code

When CON1 is activated, the style changes, that is, the view changes. So that’s it for controllers, but let’s look at the View part of MVC, which is the View

View

A view is an interface to an application that provides visual representation and interaction with the user. In javaScript, a view is an illogical HTML fragment managed by a controller that handles event callbacks and embedded data. In simple terms, write HTML code in javaScript and insert HTML fragments into HTML pages. Here are two methods:

Dynamically rendered view

Create DOM elements with document.createElement, set their contents and append them to the page, For example, var views = document.getelementById (‘views’) views.innerhtml = ‘// Empty var wapper = document.createElement(‘p’) Wrapper. InnerText = ‘add to views’. AppendChild (wrapper) This completes creating an element with createElement and adding it to the HTML page.

The template

Templates should be familiar if you have previous back-end development experience. For example, ejS is commonly used in NodeJS. Here is a small example of EJS, as you can see, ejS renders javascript directly to HTML

str = '<h1><%= title %></h1>'
ejs.render(str, {
    title: 'ejs'
});
Copy the code

So the result of this rendering is

ejs

var addChange = function (ob) { ob.change = function (callback) { if (callback) { if (! this._change) this._change = {} this._change.push(callback) } else { if (! this._change) return for (var i = this._change.length - 1; i >= 0; i--) { this._change[i].apply(this) } } } }Copy the code

Let’s look at a practical example

var addChange = function (ob) { ob.change = function (callback) { if (callback) { if (! this._change) this._change = {} this._change.push(callback) } else { if (! this._change) return for (var i = this._change.length - 1; i >= 0; i--) { this._change[i].apply(this) } } } } var object = {} object.name = 'Foo' addChange(object) object.change(function () { console.log('Changed! ', this) // Update view code}) obejct.change() object.name = 'Bar' object.change()Copy the code

This implements the execution and firing of the change event. I believe you have a good understanding of MVC, so let’s study the MVVM pattern.

MVVM

Nowadays, the mainstream Web framework basically adopts MVVM pattern, why give up MVC pattern, and switch to MVVM pattern? In the previous MVC, we mentioned that one controller corresponds to one view, and the controller is managed by a state machine. There is a problem here. If the project is large enough, the amount of code in the state machine will become bloated and difficult to maintain. Another problem is performance. In MVC, we operate DOM a lot, which will reduce page rendering performance, slow loading speed and affect user experience. Finally, when the Model changes frequently and developers actively update the View, data maintenance becomes difficult. The world is created by lazy people, and in order to reduce the workload and save time, it is important to have an architectural pattern that is more suitable for front-end development. At this time, the APPLICATION of MVVM mode in the front end came into being. MVVM makes the user interface and logic separation clearer. The following is a diagram of MVVM, which is composed of Model, ViewModel, and View.

View

A View is used as a View template to define structure and layout. It doesn’t process the data itself, it just presents the data in the ViewModel. In addition, in order to be associated with the ViewModel, all you need to do is declare the data binding, declare the directive, and declare the event binding. This is evident in today’s popular MVVM development frameworks. In the sample figure, we can see that the ViewModel and View are bidirectional, meaning that changes in the ViewModel are reflected in the View, and changes in the View can change the ViewModel data values. How do you implement bidirectional binding, for example with this input element:

<input type='text' yg-model='message'>
Copy the code

As the user changes the value in the Input, so does the message in the ViewModel, enabling one-way data binding from View to ViewModel. Here are some ideas:

  1. Scan to see which nodes have the YG-XXX attribute
  2. These nodes are automatically assigned events such as onchange
  3. Update the data in the ViewModel, for example viewModel.message = xx.innerText

A ViewModel can bind to a View as follows:

<p yg-text='message'></p>
Copy the code

The values displayed in rendered P are the message variable values in the ViewModel. Here are some ideas:

  1. First register the ViewModel
  2. Scan the entire DOM Tree to see which nodes have the yG-xxx attribute
  3. Records the implicit relationship between these unidirectional bound DOM nodes and the ViewModel
  4. Assign with innerText,innerHTML = viewModel.message

ViewModel

The ViewModel connects the View to the Model and handles the logic in the View. In THE MVC framework, the ViewModel interacts with the Model by calling methods in the Model. However, in MVVM, there is no direct relationship between the View and Model. In MVVM, the ViewModel gets data from the Model and then applies it to the View. Compared to MVC’s many controllers, this pattern is obviously easier to manage data and less chaotic. Another is to handle events in the View, such as when the user clicks on a button, the action triggers the ViewModel’s behavior and performs the corresponding action. The behavior might include changing the Model and rerendering the View.

Model

Model layer, corresponding to the domain Model of the data layer, it mainly does the synchronization of the domain Model. The client and server business models are synchronized through Ajax/ FETCH and other apis. In interlayer relationships, it is primarily used to abstract the Model of the view in the ViewModel.

MVVM simple implementation

Effect:

<div id="mvvm">
	<input type="text" v-model="message">
	<p>{{message}}</p>
	<button v-click='changeMessage'></button>
</div>
<script type="">
	const vm = new MVVM({
		el: '#mvvm',
		methods: {
			changeMessage: function () {
				this.message = 'message has change'
			}
		},
		data: {
			message: 'this is old message'
		}
	})
</script>
Copy the code

Here, for simplicity, some of Vue’s methods are borrowed

Observer

MVVM saves us the need to manually update the view and re-render it whenever the value changes, so we need to detect changes in the data. Here’s an example:

hero = {
	name: 'A'
}
Copy the code

When we access hero.name, we print out some information:

hero.name 
// I'm A
Copy the code

When we make changes to hero.name, we also print out some information:

hero.name = 'B'
// the name has change
Copy the code

So that’s how we’ve actually observed the data. Angular implements data observation using dirty checking, which compares the old ViewModel and makes changes when the user does something that might change the ViewModel. In Vue, data hijacking is done, where object.defineProperty () is triggered when data is fetched or set. Here we adopt the Vue data observation method, which is simpler. Here’s the code

function observer (obj) { let keys = Object.keys(obj) if (typeof obj === 'object' && ! Array.isArray(obj)) { keys.forEach(key => { defineReactive(obj, key, obj[key]) }) } } function defineReactive (obj, key, val) { observer(val) Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function () { console.log('I am A') return val }, set: function (newval) { console.log('the name has change') observer(val) val = newval } }) }Copy the code

Bring hero into the observe method, and the result is as expected. The data is then checked and the subscriber is notified. To notify subscribers, we need to implement a message subscriber, maintain an array to collect subscribers, notify() is triggered by data changes, and update() is triggered by subscribers. The improved code looks like this:

function defineReactive (obj) { dep = new Dep() Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function () { console.log('I am A') Dep.target || dep.depend() return val }, set: function (newval) { console.log('the name has change') dep.notify() observer(val) val = newval } }) } var Dep = function  Dep () { this.subs = [] } Dep.prototype.notify = function(){ var subs = this.subs.slice() for (var i = 0, l = subs.length; i < l; i++) { subs[i].update() } } Dep.prototype.addSub = function(sub){ this.subs.push(sub) } Dep.prototype.depend = function(){ if (Dep.target) { Dep.target.addDep(this) } }Copy the code

This is similar to the Vue source code, which completes adding subscribers to the subscriber, and notifying subscribers. When I looked at the Vue source code, the question that puzzled me for a long time was where the Dep came from in the get method. The target variable is used to add a subscriber to the subscriber. The subscriber here is Wacther, and Watcher can connect to the view and update the view. Here is part of Watcher’s code

Prototype. Get = function(key){dep.target = this this.value = obj[key] // Trigger get to add subscriber dep.target = null // Reset};Copy the code

Compile

When talking about MVVM concepts, one of the steps in the View -> ViewModel process is to look for elements in the DOM tree that have yG-XX. This section is all about parsing the template, connecting the View to the ViewModel. Traversing the DOM Tree is very performance intensive, so the node EL is converted to a document fragment for parsing and compiling. After the operation is complete, add the fragment to the original real DOM node. Here’s the code

function Compile (el) { this.el = document.querySelector(el) this.fragment = this.init() this.compileElement() } Compile.prototype.init = function(){ var fragment = document.createDocumentFragment(), chid while (child.el.firstChild) { fragment.appendChild(child) } return fragment }; Compile.prototype.compileElement = function(){ fragment = this.fragment me = this var childNodes = el.childNodes [].slice.call(childNodes).foreach (function (node) {var text = node.textContent var reg = /\{\{(.*)\}\}/ / get the value in {{}} if  (reg.test(text)) { me.compileText(node, RegExp.$1) } if (node.childNodes && node.childNodes.length) { me.compileElement(node) } }) } Compile.prototype.compileText = function (node, vm, exp) { updateFn && updateFn(node, vm[exp]) new Watcher(vm, exp, Function (value, oldValue) {function (value, oldValue) {function (value, oldValue) {function (value, oldValue) { Function updateFn(node, value) {node.textContent = value} function updateFn(node, value) {node.textContent = value}Copy the code

The fragment is compiled successfully, and changes in the ViewModel can cause changes in the View layer. Now, the implementation of Watcher, now that we’ve talked about the get method, let’s look at other methods.

Watcher

Watcher is the bridge between Observer and Compile. You can see that in the Observer, you have added yourself to the subscriber. Sub.update () is called when dep.notice() occurs, so we need an update() method that triggers the callback in Compile to update the view when the value changes. Here is a concrete implementation of Watcher

Var Watcher = function Watcher (vm, exp, cb) {this.vm = vm this.cb = cb this.exp = exp Prototype = {update: function () {this.run()}, addDep: function (dep) { dep.addSub(this) }, run: function () { var value = this.get() var oldVal = this.value if (value ! == oldValue) {this.value = value this.cb.call(this.vm, value, oldValue)}}, get: Function () {dep.target = this value = this.vm[exp] // Trigger getter dep.target = null return value}}Copy the code

In the above code, Watcher connects the Observer to Compile. When a value changes, Watcher informs Watcher, and then calls the update method. The callback in Watcher() is invoked to update the view. The most important part is done. Add an MVVM constructor and it’s ok. I recommend an article to implement MVVM on your own, which is more detailed here.

conclusion

Ok, that’s the end of this article. I hope readers can get a clearer understanding of the current framework of the front end through comparison. Thank you for your attention