MVVM introduction and demonstration
- easyUI
- knockout
- smartclient
- exit.js
During the interview, the interviewer will ask you to describe the MVVM you know?
What is MVVM implemented in Vue?
OK, let's solve the problem
-
First of all, the first M refers to the Model, that is, the * * data Model. In fact, it refers to the data * * in the Vue component instance. However, we have defined this data from the beginning, which is called responsive data
-
The second V refers to the View, that is, the * * page View. Changing to Vue is the DOM object converted from our template**
-
The third VM refers to * * ViewModel, which is the manager of View and data. It manages the work of changing our data into View. In Vue, it refers to our current Vue instance, a bridge between Model data and View view View communication**
- In a word: data driven view, data change = > View update, bidirectional binding view update = > data change
Vue = = > MVVM = > bidirectional data binding = > this. Name = 'Zhang San'
React = > MVVM = > one way data binding = > only from data = > view = > this. Setstate ({Name: 'Zhang San'})
<!-- view --> <template> <div>{{ message }}</div> </template> <script> // Model common data object export default { data () { return { message: 'Hello World' } } } </script> <style> </style>
MVVM responsive principle Object.defineProperty() - basic use
Next, we will focus on the principle and implementation of MVVM. Vuejs official website gives the principle and implementation of MVVM
From the above documents, we can see that Vue's responsive principle (MVVM) is actually the following sentence:
When you pass a normal JavaScript object into the Vue instance as the data option, Vue will traverse all the properties of the object and use the Object.defineProperty Turn all these attributes into getter/setter . Object.defineProperty is a feature of ES5 that cannot be shim ed, which is why Vue does not support IE8 and earlier browsers.
From the above description, we found several keywords, object.defineproperty getter / setter
What is Object.defineProperty?
Definition: the Object.defineProperty() method will directly define a new property on an object, or modify an existing property of an object, and return the object.
Syntax: Object.defineProperty(obj, prop, descriptor)
Parameter: Obj = > the object on which to define the attribute.
Prop = > property name to be added or modified
Descriptor = > the attribute descriptor to be defined or modified.
Return value: the object passed to the function. That is, the obj object passed in
From the notes above, we can see what parameters need to be learned
obj is an object that can be new Object() or {}
prop is the property name, which is a string
What is the descriptor? What are the attributes
There are two main forms of attribute descriptors in object: data descriptors and access descriptors. A data descriptor is an attribute with a value that may or may not be writable. The access descriptor is a property described by the getter setter function. A descriptor must be one of these two forms; it cannot be both.
The above is the official description. It tells us that there are * * two patterns in the design of defineProterty, one is data description, the other is access description**
Descriptors must be one of the two. They cannot be both. That is to say, a mountain cannot have two tigers, neither can a mountain have two tigers
Let's write a simple example of * * data descriptor * *
// Object.defineProperty(obj,prop, desciptor) // Descriptor = > data descriptor access descriptor var obj = { name: 'Cao Yang' } var o = Object.defineProperty(obj, 'weight', { // Descriptor is an object // Data description access description value: '280kg' // Data descriptor value }) console.log(o)
Next, we will make a detailed analysis
Object.defineProperty() - data descriptor mode
What are the properties of the data descriptor?
- Value = > the value corresponding to the attribute. Can be any valid JavaScript value (value, object, function, etc.). Unfind by default
- Writable = > value can only be changed by the assignment operator if and only if the writable of the attribute is true. The default is false.
These two? Anything else?
- Configuration = > when and only when the configuration of the attribute is true, the attribute descriptor can be changed, and the attribute can also be deleted from the corresponding object. The default is false. Decide whether writable can be changed
- Enumerable = > the attribute can appear in the enumeration property of an object only if and only if the enumerable of the attribute is true. The default is false.
Why are configurable and enumerable different from value and writable?
Because these two attributes can appear not only in the data descriptor, but also in the access descriptor
We write a writable property and an unwritten property through the writeable and value properties
var obj = { name: 'Cao Yang' } Object.defineProperty(obj, 'money', { value: "10k" // Salary is immutable at this time }) Object.defineProperty(obj, 'weight', { value: '150 Jin', // Ten thousand hairs writable: true }) obj.money = '20k' obj.hair = '200 Jin' console.log(obj)
Next, we want to make an immutable property variable
var obj = { name: 'Cao Yang' } Object.defineProperty(obj, 'money', { value: '10k', // Salary is immutable at this time configurable: true // The writeable property can only be changed if it is true here }) Object.defineProperty(obj, 'weight', { value: '150 Jin', // Ten thousand hairs writable: true }) obj.money = "20k" obj.weight = '200 Jin' console.log(obj) Object.defineProperty(obj, 'money', { writable: true }) obj.money = '20k' console.log(obj)
Next, we want to traverse to the two newly added attributes when traversing
var obj = { name: 'Cao Yang' } Object.defineProperty(obj, 'money', { value: '10k', // Salary is immutable at this time configurable: true, enumerable: true }) Object.defineProperty(obj, 'weight', { value: '150 Jin', // Ten thousand hairs writable: true, enumerable: true }) obj.money = "20k" obj.weight = '200 Jin' console.log(obj) Object.defineProperty(obj, 'money', { writable: true }) obj.money = '20k' console.log(obj) for(var item in obj) { console.log(item) }
Object.defineProperty() - access descriptor mode
In the previous section, the unique attributes of data descriptors are value and writable, which means that in the access description mode
value and writable attributes cannot appear
What are the properties of the storage descriptor?
- get a method that provides a getter for a property. If there is no getter, it is undefined. When the property is accessed, the method will be executed. No parameters will be passed in when the method is executed, but this object will be passed in (because of inheritance, this here is not necessarily the object defining the property).
- set a method that provides a setter for a property. If there is no setter, it is undefined. This method is triggered when the property value is modified. The method takes a unique parameter, the new parameter value of the property.
get/set is actually our most common way to read values and set values
Call get method when reading value
Call set method when setting value
Let's make a way to read the settings through get and set
var obj = { name: 'Cao Cao' } var wife = 'Little Joe' Object.defineProperty(obj, 'wife',{ get () { return wife }, set (value) { wife = value } }) console.log(obj.wife) obj.wife= 'Big Joe' console.log(obj.wife)
However, what do we want to do with traversal? Note that when storing descriptors, they still have the configurable and enumerable properties,
Still configurable
var obj = { name: 'Cao Cao' } var wife = 'Little Joe' Object.defineProperty(obj, 'wife',{ configurable:true, enumerable: true, get () { return wife }, set (value) { wife = value } }) console.log(obj.wife) obj.wife= 'Big Joe' console.log(obj.wife) for(var item in obj) { console.log(item) }
Object.defineProperty() - simulate vm object
Through two sections, I learned the basic use of defineProperty. Next, we will simulate the effect of Vue instantiation through defineProperty
When Vue is instantiated, we explicitly assign data to data, but it can be accessed and set through * * vm instance. Property * *
How?
In fact, this is achieved through Object.defineProperty
var data = { name: "Zhang San" }; var vm = {}; Object.defineProperty(vm, "name", { set(value) { data.name = value; }, get() { return data.name; } }); console.log(vm.name); vm.name = "Li Si"; console.log(vm.name);
In the above code, we implement the data agent in vm, change the name in data directly to vm, that is, change the data
Summary: We proxy the data in data in the access descriptors of set and get,
MVVM = > data agent = > object. Defineproperty = > access descriptor get / set = > agent data
MVVM not only needs to acquire these data, but also update them to DOM in a responsive manner. In other words, when the data changes, we need to reflect the data * * to the view
Through debugging, we found that we can monitor the change of data in the set function. Only when the data changes, we need to notify the corresponding view to update
So how to notify? What technology to use? In the next section, we will bring the publish and subscribe mode
Introduction to the publish and subscribe mode
What is the publish and subscribe mode?
In fact, we have used it many times. Publishing / subscribing means that someone * * publishes news * * and someone subscribes to news. When it comes to data level, it means more = > more
That is, A program can trigger multiple messages or subscribe to multiple messages
We have used an eventBus in project 1 and project 2 of the black horse headline, which is the embodiment of the publish and subscribe mode
What do we do with this model?
In the last section, we have been able to capture the changes of data. Next, we will try to change our view by publishing and subscribing to this mode when the data changes
Let's write out some elements of the core code of this publish and subscribe
First, we want to get the publish / subscribe object through instantiation
Post $emit
Subscription $on
According to the above thought, we get the following code
// Create a constructor function Events (){} // Subscription message Events.prototype.$on = function(){} // Publish news Events.prototype.$emit = function(){}
Implementation of publish and subscribe mode
function Events () { this.subs = {} } Events.prototype.$on = function (eventName, fn) { this.subs[eventName] = this.subs[eventName] || [] this.subs[eventName].push(fn) } Events.prototype.$emit = function (eventName, ...params) { if(this.subs[eventName]) { this.subs[eventName].forEach(fn => { //fn.apply(this, [...params]) //fn.call(this, ...params) fn.bind(this,...params)() }); } } var test = new Events() test.$on("updateABC", function(a,b,c){ console.log(a+'-'+b+'-'+c) console.log(this) }) var go = function(){ test.$emit("updateABC", 1,2,3) }
Here, the call/apply/bind method is used to modify this point in the function
Using the publish and subscribe mode, you can realize that when an event is triggered, many people will be notified to do something, and the thing you do in Vue is to update the DOM
MVVM implementation DOM review
We have learned Object.defineProperty and publish / subscribe mode, and almost have the ability to write an MVVM,
But before implementing MVVM, let's review the meaning and structure of View, that is, Dom
What is DOM?
document object model
What is the role of Dom?
You can use * * object * * to manipulate page elements
What are the types of object nodes in Dom
You can check it with the following small example
<div id="app"> <h1>Our wills unite like a fortress,Common anti epidemic situation</h1> <div> <span style='color:red;font-weight: bold;'>fairly tall:</span> <span>Wish all students a bright future</span> </div> </div> <script> var app = document.getElementById("app") console.dir(app) </script>
From the above output, we can see that
Node type of element type nodeType is 1, text type is 3, and every content in document object is * * node**
childNodes is the child value of all nodes, all elements = > nodeType = > node type
All child nodes are placed under the property childNodes. ChildNodes is a pseudo array = > pseudo array has no array method and len gt h property
What is the attribute set of all tags?
attributes
What do we do to analyze DOM objects? The data capture and publish subscription we prepared earlier is to update dom
Let's start by writing an example of MVVM
MVVM implementation - Implementation of Vue's constructor and data broker
The challenge is coming. We need to write a simple * * vuejs * * to improve our own technical strength
We want to implement the constructor of mvvm
The constructor imitates vuejs and has data /el respectively
Data is finally proxied to the current vm instance, which can be accessed either through vm or through this.$data
// First implement a constructor function Vue (options) { this.$options = options // All attributes give this an attribute this.$data = options.data || {} this.$el = typeof options.el ==="string" ? document.querySelector(options.el) : options.el // Proxy all data to the current instance this.$proxyData() } // Proxy data Vue.prototype.$proxyData = function () { Object.keys(this.$data).forEach(key => { Object.defineProperty(this, key, { get () { return this.$data[key] }, set (value) { if(this.$data[value] === value) return this.$data[key] = value } }) }) } var vm = new Vue({ el: '#app', data: { name: 'Cao Yang', company: 'Seize the moon' } }) console.log(vm.company) vm.company = 'Clasp the moon in the Ninth Heaven' console.log(vm.$data.company) vm.$data.company = 'Catch turtle in the sea' console.log(vm.company)
MVVM implementation - Data hijacking Observer
OK, the next step is very important. We need to do * * data hijacking * *. Who is Hijacking? Why hijacking?
In the code in the previous section, we can use vm.company = 'value' or vm.$data.name = 'value'. Where can we capture the change of data?
Whether this.data or this $data is changed to data data, so we need to * * hijack * * the data of data, that is, monitor its set
// Monitoring data Vue.prototype.observer = function () { Object.keys(this.$data).forEach(key => { let value = this.$data[key] Object.defineProperty(this.$data, key, { get () { return value }, set (newValue) { if(newValue === value) return; value = newValue // If the data changes, we need to change the view } }) }) }
Hijack data in constructor
// First implement a constructor function Vue (options) { this.$options = options // All attributes give this an attribute this.$data = options.data || {} this.$el = typeof options.el ==="string" ? document.querySelector(options.el) : options.el // Proxy all data to the current instance this.$proxyData() this.observer() // Enable monitoring data }
MVVM implementation compile template Compiler design structure
Now we have basically implemented the instantiation of data and completed the hijacking of data. Next, we need to implement several methods
When the data changes = > convert the template to the latest object according to the latest data
Determine whether it is a text node
Determine whether it is an element node
Determine whether it is an instruction
Processing element nodes
Working with text nodes
So let's define the following methods
// Compilation template Vue.prototype.compile = function () {} // Working with text nodes Vue.prototype.compileTextNode = function (node){} // Processing element nodes Vue.prototype.compileElementNode = function (node) {} // Determine whether it is a text node Vue.prototype.isTextNode = function(node) {}; // Determine whether it is an element node Vue.prototype.isElementNode = function(node) {}; // Determine whether the attribute is an instruction Vue.prototype.isDirective = function(attr) {};
MVVM implementation compile template Compiler implementation basic framework logic
We have got $el, the dom element of the page, through the constructor. Next, we can implement the basic logic of compilation
// Compilation template Vue.prototype.compile = function (rootnode) { let nodes = Array.from(rootnode.childNodes) // First convert the pseudo array to an array nodes.forEach(item => { if(this.isTextNode(item)) { this.compileTextNode(node) } if(this.isElementNode(item)) { this.compileElementNode(node) this.compile(node) // The idea of recursion } }) } // Working with text nodes Vue.prototype.compileTextNode = function (node){} // Processing element nodes Vue.prototype.compileElementNode = function (node) {} // Determine whether it is a text node Vue.prototype.isTextNode = function(node) { return node.nodeType === 3; }; // Determine whether it is an element node Vue.prototype.isElementNode = function(node) { return node.nodeType === 1; }; // Judge whether the attribute is implemented Vue.prototype.isDirective = function(attr) { return attr.startsWith("v-"); };
The basic logic of the above code is to use the method of text node when encountering text node to handle the method of element node when encountering element node
MVVM implementation compile template Compiler process text node
// Working with text nodes Vue.prototype.compileTextNode = function (node){ const text = node.textContent const reg = /\{\{(.+?)\}\}/g if(reg.test(text)) { // If the double brace is satisfied const key = RegExp.$1.trim() this.$on(key, () => { node.textContent = text.replace(reg, this[key]) // Replace the corresponding data if the brace is found }) node.textContent = text.replace(reg, this[key]) // Replace the corresponding data if the brace is found } }
Tip: in actual development, regular is not required to be memorized but can be understood
MVVM implementation compile template Compiler process element node
// Processing element nodes Vue.prototype.compileElementNode = function (node) { let atts = Array.from(node.attributes) attrs.forEach(attr => { if(this.isDirective(attr.name)) { // Determine whether it is an instruction if(attr.name === 'v-text') { node.textContent = this[attr.value] // Equal to the value of the current property } if(attr.name === 'v-model') { // v-model binds the value attribute of the form node.value = this[attr.value] } } }) }
MVVM implementation - data driven view - publish and subscribe Manager
At present, there are response data and compilation templates. We need to compile templates when the data changes
As mentioned before, this step needs to be done through publish and subscribe, so we implement publish and subscribe on the basis of Vue
// First implement a constructor function Vue (options) { this.subs = {} //Publish Subscription Manager this.$options = options // All attributes give this an attribute this.$data = options.data || {} this.$el = typeof options.el ==="string" ? document.querySelector(options.el) : options.el // Proxy all data to the current instance this.$proxyData() this.observer() // Enable monitoring data } Vue.prototype.$on= function (eventName, fn) { this.subs[eventName] = this.subs[eventName] || [] this.subs[eventName].push(fn) } Vue.prototype.$emit = function (eventName, ...params) { if(this.subs[eventName]) { this.subs[eventName].forEach(fn => { //fn.apply(this, [...params]) //fn.call(this, ...params) fn.bind(this,...params)() }); } }
MVVM implementation - driving view changes when data changes
Now everything is ready, only the east wind
Our data broker, data hijacking, template compilation, event publishing and subscription are all done. Now we only need to publish through events when the data changes, and then
Notify data to compile
// Monitoring data Vue.prototype.observer = function () { Object.keys(this.$data).forEach(key => { let value = this.$data[key] Object.defineProperty(this.$data, key, { get () { return value }, set: (newValue) => { if(newValue === value) return; value = newValue // If the data changes, we need to change the view this.$emit(key) //Trigger data change } }) }) }
Change of monitoring data
// Working with text nodes Vue.prototype.compileTextNode = function (node){ const text = node.textContent if(/\{\{(.+)\}\}/.test(text)) { // If the double brace is satisfied const key = RegExp.$1.trim() this.$on(key, () => { node.textContent = text.replace(reg, this[key]) // Replace the corresponding data if the brace is found }) node.textContent = text.replace(reg, this[key]) // Replace the corresponding data if the brace is found } } // Processing element nodes Vue.prototype.compileElementNode = function (node) { let atts = Array.from(node.attributes) attrs.forEach(attr => { if(this.isDirective(attr.name)) { // Determine whether it is an instruction if(attr.name === 'v-text') { node.textContent = this[attr.value] // Equal to the value of the current property this.$on(attr.value, () => { node.textContent = this[attr.value] // Replace the corresponding data if the brace is found }) } if(attr.name === 'v-model') { // v-model binds the value attribute of the form node.value = this[attr.value] this.$on(attr.value, () => { node.value = this[attr.value] // Replace the corresponding data if the brace is found }) } } }) }
Then we write an example to test it
MVVM implementation - view change update data
Finally, we hope to implement two-way binding, that is, when the view changes, the data changes at the same time
// Processing element nodes Vue.prototype.compileElementNode = function (node) { let attrs = Array.from(node.attributes) attrs.forEach(attr => { if(this.isDirective(attr.name)) { // Determine whether it is an instruction if(attr.name === 'v-text') { node.textContent = this[attr.value] // Equal to the value of the current property this.$on(key, () => { node.textContent = this[attr.value] // Replace the corresponding data if the brace is found }) } if(attr.name === 'v-model') { // v-model binds the value attribute of the form node.value = this[attr.value] this.$on(key, () => { node.value = this[attr.value] // Replace the corresponding data if the brace is found }) node.oninput = () => { this[attr.value] = node.value } } } }) }