- MVVM, short for Model-View-ViewModel, is an architectural pattern based on front-end development. Its core is VM, namely ViewModel, which is the bridge linking View and Model. The state change of VM can be automatically transmitted to Model and View to achieve the consistency of data and View, namely to achieve two-way data binding.
1, Vue: As the entry of data binding, integrate Observer, Compile and Watcher, use Observer to monitor the change of model data, use Compile to parse and Compile template instructions, Finally, Watcher is used to build a communication bridge between Observer and Compile to achieve data change -> view update; View Interactive Changes (INPUT) -> Bidirectional binding effect of data model changes.
Observer: a data listener that can monitor all attributes of a data object. If there is any change, it can get the latest value and notify the subscriber. Internal use of Obiect.
3. Complie: Directive parser, which scans and parses the instructions of each element node, replaces data according to the directive template, and binds the bridge between Observer and Complie, subscribes and receives notification of each attribute change, and executes the corresponding callback function of the directive binding.
Watcher: The subscriber, acting as a bridge between Observer and Complie, can subscribe to and receive notification of each property change and execute the corresponding callback function of the directive binding.
5, Dep message subscriber, internal maintenance of an array, used to collect subscribers (Watcher), data changes trigger notify function, and then call the update method of the subscriber.
When new Vue() is executed, the Vue iterates through data’s attributes and uses the Get () and set() methods in Object.defineProperty to listen for the data, which is called data hijacking, and turn the data into responsive data. The Complie instruction parser in Vue will scan and parse all nodes, add element nodes and text nodes into the memory fragment, replace the element nodes with instructions and nodes using interpolation and bind corresponding update functions, and then return all nodes to the web page to complete the initialization of the view. And in the process, subscribe to Watcher to update the view, at which point Watcher adds itself to the message Subscriber (Dep) to complete the initialization.
When data changes, the set() method in the Observer is triggered, and the set() method calls dep.notify (). The Dep traverses all subscribers and calls the update() method of the subscriber, who is notified to update the view and data.
The following is a simple MVVM principle simulation process.
Create vueMVVM. Js
// Dep class Dep {constructor() { this.subs = []; AddSub (watcher) {this.subs.push(watcher)} addSub(watcher) {this.subs.push(watcher)notify() {this.subs.foreach (watcher => watcher.update())}} constructor(vm, expr, cb) {this.vm = vm; this.expr = expr; this.cb = cb; This.oldvalue = this.get(); this.oldValue = this.get(); } // The method to get the stateget() {
Dep.target = this;
let value = CompilerUtil.getVal(this.vm, this.expr);
Dep.target = null;
returnvalue; } // When the state changes, the observer's update method is calledupdate() {
let newVal = CompilerUtil.getVal(this.vm, this.expr);
if(newVal ! == this.oldValue) { this.cb(newVal); New class Observer {constructor(data) {this. Observer (data)} Observer (data) {constructor(data) {if (data && typeof data == 'object') {
for (let key indata) { this.defindReactive(data, key, data[key]) } } } defindReactive(obj, key, value) { this.observer(value); // If a data is an object, we also need to make the data in that object responsiveletdep = new Dep(); DefineProperty (obj, key, {// When you get school, get is calledget() {
Dep.target && dep.subs.push(Dep.target)
// console.log("get....")
returnValue}, // will be called when you set schoolset
set: (newVal) => {// If the assigned value is the same as the old value, it is not reassignedif(newVal ! = value) { this.observer(newVal) value = newVal dep.notify(); }}} class Compiler {constructor(el,)}} // Find the element node with instructions and interpolation (v-text) text node. vm) { this.el = this.isElementNode(el) ? el : document.querySelector(el) this.vm = vm; // At this point, all nodes are located in the document shardletfragment = this.node2fragment(this.el); This.compile (fragment); this.compile(fragment); This.el.appendchild (fragment)} isDirective(attrName) {return attrName.startsWith("v-"} // compileElement node compileElement(node) {letattributes = node.attributes; // console.log(node); [...attributes]. ForEach (attr => {let {
name,
value: expr
} = attr;
// console.log(value) // school.name
if (this.isDirective(name)) {
// console.log(name) // v-model
let [, directive] = name.split("-"); // console.log(directive) // console.log(node) // Now you can find the element node with the directive CompilerUtil[directive](node, expr, this.vm); }})} // compileText node compileText(node) {// console.dir(node) // get all text nodesletcontent = node.textContent; // console.log(content) gets the contents of all text nodesletreg = /\{\{(.+?) \} \} /; // {} has special meaning in re, need to escape reg.test(content) // Return ture if content satisfies the re we wrote, otherwise returnfalse
if (reg.test(content)) {
// console.log(content) // {{school.name}} {{school.age}}
// console.log(node) // "{{school.name}}"// console.log(content) // {{school.name}} content is the content of the text node CompilerUtil['text'](node, content, this.vm)}}letchildNodes = node.childNodes; [...childNodes]. ForEach (child => {// Child means each node // If child element node, call compileElementif(this.isElementNode(child)) { this.compileElement(child); This.compile (child)} this.compile(child) {this.compile(child)}elseCall compileText this.piletext (child)}})} node2Fragment (node) {// call compileText this.piletext (child)}}let fragment = document.createDocumentFragment();
let firstChild;
while (firstChild = node.firstChild) {
fragment.appendChild(firstChild)
}
return fragment;
}
isElementNode(node) {
returnnode.nodeType === 1; }} // CompilerUtil = {getVal(vm, expr) {CompilerUtil = {getVal(vm, expr) {return expr.split(".").reduce((data, current) => {
return data[current]
}, vm.$data); }, // Set the datasetVal(vm, expr, // console.log(expr) // school.name // console.log(value) // beida1 // console.log(expr.split(".")) / / /"school"."name"]
expr.split("."). Reduce ((data, current, index, arr) => {// first time: data is the school object"school"Index is 0 arr is array ["school"."name"] // 第2 行 : Data is undefined cureent is undefined"name"Index is 1 arr is array ["school"."name"]
// console.log(data,current,index,arr)
if (index == arr.length - 1) {
// console.log(current) // name
// console.log(data)
return data[current] = value
// console.log(data[current])
}
// console.log("...")
return data[current]
}, vm.$data)
},
model(node, expr, vm) {
let fn = this.updater["modelUpdater"New Watcher(vm, expr, (newVal) => {fn(node, newVal)}) node.adDeventListener ()"input", (e) => {
// console.log(e.target.value)
let value = e.target.value
this.setVal(vm, expr, value);
})
let value = this.getVal(vm, expr)
fn(node, value);
},
htmlGetContentValue (vm, expr) {getContentValue(vm, expr) {returnexpr.replace(/\{\{(.+?) \}\}/g, (... args) => {return this.getVal(vm, args[1])
})
},
text(node, expr, vm) {
let fn = this.updater["textUpdater"]
letcontent = expr.replace(/\{\{(.+?) \}\}/g, (... args) => { new Watcher(vm, args[1], () => { fn(node, this.getContentValue(vm, expr)); })returnthis.getVal(vm, args[1]) }) fn(node, content); }, // update data updater: {modelUpdater(node, value) {node.value = value},htmlUpdater() {}, // Handle the text node textUpdater(node, Node. textContent = value}}} // The VUE class is responsible for the overall height of class VUE {constructor(options) {this.$el = options.el;
this.$data = options.data;
if (this.$elNew Observer(this) {// Change data into a responsive new Observer(this).$data)
// console.log(this.$data)
// school: {name: "beida", age: 100} // Use vm to proxy VM.$data
this.proxyVm(this.$data)
new Compiler(this.$el}} proxyVm(data) {for (let key in data) { // {school:{name:beida,age:100}}
// school---[object Object]-----[object Object]
// console.log(key+"-"+data[key]+"-- -- -- -- --"+data)
Object.defineProperty(this, key, {
// vm.school
get() {
return data[key]
}
})
}
}
}
Copy the code
Introduce vuemvm. js to HTML pages to verify two-way binding of data.
<! DOCTYPE html> <html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="Width = device - width, initial - scale = 1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">
<input type="text" v-model="school.name">
<input type="number" v-model="school.age"> <div>{{school.name}}</div> <div>{{school.age}}</div> <ul> <li>1</li> <li>2</li> </ul> </div> <! -- <script src="https://cdn.bootcss.com/vue/2.6.10/vue.common.dev.js"></script> -->
<script src="./vueMVVM.js"></script>
<script>
let vm = new Vue({
el: "#app",
data: {
school: {
name: "beida",
age: 100
}
},
methods: {
},
computed: {
}
})
</script>
</body>
</html>
Copy the code
At this point, MY understanding of MVVM is complete, and I will add new understanding later.