preface
This article was first published on the front-end development blog
MVVM is a necessary mode for daily front-end business development in the current era (related frameworks such as React, Vue, Angular, etc.). Using MVVM allows developers to focus more on business logic rather than dom manipulation. Although it is 9012 now, the introduction of MVVM related principles has been widely used, but for the purpose of learning the basic knowledge (vue3.0 using proxy implementation is still under development), after referring to the overall idea of vuue.js, Diy implementation of a simple proxy implementation of MVVM.
This project code has been open source on Github, the project is continuing to improve, welcome to exchange and learn, if you like, please click star!
The final result
<html>
<body>
<div id="app">
<div>{{title}}</div>
</div>
</body>
</html>
Copy the code
import MVVM from '@fe_korey/mvvm';
new MVVM({
view: document.getElementById('app'),
model: {
title: 'hello mvvm! '
},
mounted() {
console.log('Main program compiled, welcome to MVVM! '); }});Copy the code
An overview of structure
Complier
The module parses, collects instructions, and initializes the viewObserver
The module implements data monitoring, including adding subscribers and notifying subscribersParser
The module implements the parse instruction and provides the update method of the update view of the instructionWatcher
Module to achieve the establishment of instruction and data associationDep
The module implements a subscription center that collects and triggers a subscription list of data model values
The process is as follows: Complier collects and compiles instructions, selects different Parser according to different instructions, updates the initial view according to the changes of the subscription data of Parser in Watcher. The Observer listens for changes and reports them to Watcher, who in turn reports the changes to the corresponding Parser update function for view refreshes.
Module,
Complier
-
Pass the entire data model, data, into the Observer module for data listening
this.$data = new Observer(option.model).getData(); Copy the code
-
Loop through the DOM, scanning and extracting all instructions for each DOM element
function collectDir(element) { const children = element.childNodes; const childrenLen = children.length; for (let i = 0; i < childrenLen; i++) { const node = children[i]; const nodeType = node.nodeType; if(nodeType ! = =1&& nodeType ! = =3) { continue; } if (hasDirective(node)) { this.$queue.push(node); } if(node.hasChildNodes() && ! hasLateCompileChilds(node)) { collectDir(element); }}}Copy the code
-
Compile each instruction, selecting the corresponding Parser
const parser = this.selectParsers({ node, dirName, dirValue, cs: this }); Copy the code
-
Pass the resulting Parser to Watcher and initialize the view of the DOM node
const watcher = new Watcher(parser); parser.update({ newVal: watcher.value }); Copy the code
-
$mounted()
this.$mounted(); Copy the code
-
Using document fragments document. CreateDocumentFragment () instead of a real dom node fragments, upon the compile all instructions, the real dom node to be supplemented document fragments back
let child; const fragment = document.createDocumentFragment(); while ((child = this.$element.firstChild)) { fragment.appendChild(child); } // After parsing this.$element.appendChild(fragment); delete $fragment; Copy the code
Parser
-
In the Complier module compiled instructions, select a different listening parser to parse, Currently include ClassParser DisplayParser, ForParser IfParser, StyleParser, TextParser, ModelParser, OnParser, OtherParser parsing module, etc.
switch (name) { case 'text': parser = new TextParser({ node, dirValue, cs }); break; case 'style': parser = new StyleParser({ node, dirValue, cs }); break; case 'class': parser = new ClassParser({ node, dirValue, cs }); break; case 'for': parser = new ForParser({ node, dirValue, cs }); break; case 'on': parser = new OnParser({ node, dirName, dirValue, cs }); break; case 'display': parser = new DisplayParser({ node, dirName, dirValue, cs }); break; case 'if': parser = new IfParser({ node, dirValue, cs }); break; case 'model': parser = new ModelParser({ node, dirValue, cs }); break; default: parser = new OtherParser({ node, dirName, dirValue, cs }); } Copy the code
-
Different parsers provide a different view refresh function, update(), to update the DOM view
//text.js function update(newVal) { this.el.textContent = _toString(newVal); } Copy the code
-
OnParser parses event bindings, corresponding to the Methods field in the data model
/ / https://github.com/zhaoky/mvvm/blob/master/src/core/parser/on.ts el.addEventListener(handlerType, e => { handlerFn(scope, e); }); Copy the code
-
ForParser parses arrays
/ / https://github.com/zhaoky/mvvm/blob/master/src/core/parser/for.ts Copy the code
-
ModelParser supports input[text/password] & Textarea, Input [radio], INPUT [checkbox],select.
-
Data changes update form: The update method triggers an update of the form’s value, just as with any directive update view
function update({ newVal }) { this.model.el.value = _toString(newVal); } Copy the code
-
Form changes update data: Listen for form change events such as input,change, and set the data model in callbacks
this.model.el.addEventListener('input', e => { model.watcher.set(e.target.value); }); Copy the code
-
Observer
MVVM
The core of the model is generally passedObject.defineProperty
的get
.set
Method to listen on data inget
To add subscribers,set
Notifies the subscriber to update the view. Adopted in this projectProxy
To achieve data monitoring, there are three advantages:Proxy
You can listen directly on objects instead of propertiesProxy
You can listen for changes in the array directlyProxy
There are up to 13 methods of interception,Refer toThe disadvantage is compatibility issues and failure to passpolyfill
Smooth. Refer tocompatibility
- Pay attention to
Proxy
It only listens for each property of itself. If the property is an object, that object is not being listened on, so recursive listening is required - After listening is set, one is returned
Proxy
Replace the original data object
var proxy = new Proxy(data, {
get: function(target, key, receiver) {
// Add subscribers if conditions are met
dep.addDep(curWatcher);
return Reflect.get(target, key, receiver);
},
set: function(target, key, value, receiver) {
// Notify subscribers if conditions are met
dep.notfiy();
return Reflect.set(target, key, value, receiver); }});Copy the code
Watcher
-
In the Complier module, the instruction of each parsed Parser is directly bound to the data model, and the get listener of the Observer is triggered to add the subscriber (Watcher).
this._getter(this.parser.dirValue)(this.scope || this.parser.cs.$data); Copy the code
-
As the data model changes, It triggers -> Observer set listener -> Dep’s Notfiy method (notifies subscribers of all subscription lists) -> Execute all Watcher update methods on subscription lists -> execute corresponding Update on Parser -> Finish updating the view
-
The set method in Watcher is used to set the bidirectional binding value. Note the access level
Dep
MVVM
The subscription center, where a subscription list for each property of the data model is collected- Includes adding subscribers, notifying subscribers and other methods
- It’s essentially a publish/subscribe model
class Dep {
constructor() {
this.dependList = [];
}
addDep() {
this.dependList.push(dep);
}
notfiy() {
this.dependList.forEach(item= >{ item.update(); }); }}Copy the code
Afterword.
At present, the MVVM project only realizes the functions of data binding and view update. Through the implementation of this simple wheel, the dom operation, proxy, publish and subscribe mode and other basic knowledge are understood again, so as to make up for omissions. At the same time, welcome to discuss and exchange, we will continue to improve!