1.MVVM
MVVM => Model(data) -view (View) -viewModel (View)
-
Model => data, View => Template, ViewModel => new vue ()…
-
MVVM has data bidirectional binding as its core idea. There is no correlation between the View and the Model, they interact through the ViewModel bridge.
-
The interaction between the Model and ViewModel is two-way, with changes to the View being synchronized to the Model and changes to the Model being synchronized to the View immediately.
-
When the user manipulates the View, the ViewModel senses the change and notifies the Model of the change. Conversely, when the Model changes, the ViewModel can also sense the change and make the View update accordingly.
2. Write a vue. Js
The following code simply implements vUE’s bidirectional data binding, computed, and Methods functions. Note: CompierUtil is the extracted public method.
Code comments are very detailed (you can leave questions)
- index.html
<! 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">
<div>{{school.name}}{{school.age}}</div>
<div>{{school.age}}</div>
<div>{{desc}}</div>
<div v-html="message"></div>
<ul>
<li>1</li>
<li>2</li>
</ul>
<button v-on:click="change"></button> </div> <! -- <script src="./vue/dist/vue.js"></script> -->
<script src="./MVVM.js"></script>
<script>
// console.log(Vue);
var vm = new Vue({
el: '#app',
data: {
school:{
name: 'alex',
age:'18'
},
message: '< h1 > haha < / h1 >'
},
computed:{
desc() {
return this.school.name + 'bad'
}
},
methods:{
change() {
this.school.age = 100
}
}
});
</script>
</body>
</html>
Copy the code
- MVVM.js
Create a new mvvm.js, pass the parameters to MVVM through options, and fetch el and data bound to MVVM private variables and data.
// Create your own vue class. Class vue {constructor(options) {// options: instantiate this as an argument.$el = options.el;
this.$data = options.data;
let computed = options.computed;
letmethods = options.methods; // Check whether the root element existsif (this.$el) {// Data hijacking, add a DEP new Observer(this) to each attribute.$data) // Proxy computed data to this.$dataSo that the data can be accessed directly through this.xxxfor (let key in computed) {
Object.defineProperty(this.$data, key, {
get:() => {
returnComputed [key].call(this)}})} // Proxy methods data to instances so that data can be accessed directly through this.xxxfor (let key in methods) {
Object.defineProperty(this, key, {
get:() => {
return[key]}})}$dataProxyVm (this).$data// Compile template data new Compiper(this.$el, this)}} proxyVm(data) {// access this. XXX is this.$data.xxx
for (let key inDefineProperty (this, key, {// If and only if the property is configured differentlytrue, the attribute descriptor can be changed, and the attribute can also be deleted from the corresponding object. The default isfalse. configurable:false// If and only if enumerable istrueThe property can appear in the enumeration properties of the object. The default isfalse. enumerable:false.get() {return data[key]
},
set(newValue){
data[key] = newValue
}
})
}
}
}
Copy the code
Implement an Observer that listens to all data and publishes notifications of changes to data;
Class Observer {constructor(data) {this. Observer (data)} Observer (data) {//if (data && typeof data == 'object') {
for (let key inData) {this.definereActive (data, key, data[key])}} // Redefine defineReactive(obj, key, value) {// Define defineReactive(obj, key, value) { This.observer (value) // instantiates a subscriber to the scope of the current property. This DEP can only be called by the current propertylet dep = new Dep();
Object.defineProperty(obj, key, {
get() {// check console.log(dep.target) // Store the subscriber (to avoid duplicate storage, execute when target exists, Set to null in watcher after executing once) dep.target && dep.addSub(dep.target)return value
},
set: (newValue) => {// executes when the newValue changesif(newValue ! This.observer (newValue) {this.observer(newValue) // overwrite the old value with the newValue value = newValue // notify the subscriber of this property of data update dep.notify(); }})}}Copy the code
Compiper implementation, template compilation, including compilation elements (instructions), compilation text, etc., to achieve the purpose of initialization view, and add subscribers
Class Compiper {constructor(el, constructor) Vm) {// Document.getelementById gets a dynamic document.querySelector gets a static root element this.el = this.iselementNode (el)? el : document.querySelector(el); this.vm = vm; // Get the root element into memoryletFragment = this.nodeTofragment (this.el) // Replace the data in the template with the expression ({{school.name}}\v-model="school.name"This.el.appendchild (fragment)} compier(node) {this.el.appendchild (fragment) {this.el.appendchild (fragment)}letchildNode = node.childNodes; ForEach (child => {// one layer of child elements, not including the child array [...childNode].if(this.iselementNode (child)) {this.iselementNode (child) {this.iselementNode (child)}elseThis.piertext (child)}}) {// startsWith es6return attrName.startsWith('v-'} // compierElement(node) {// Get element attributesletattributes = node.attributes; // Class array [...attributes]. ForEach (attr => {// console.log(attr);type=text v-model=school.name
let{name, value: expr} = attr // Check whether attribute is an instructionif (this.isDirective(name)) { // v-model v-html v-bind v-on:click
let [, directive] = name.split(The '-') // directive:model\html\bind\on:click // Split if on:clicklet [directiveName, eventName] = directive.split(':'CompierUtil[directiveName](node, expr, this.vm, eventName)}})} compierText(node) {// Get the contents of the text nodelet content = node.textContent;
if(/ \ {\ {(. +?) \}\}/.test(content)) {'text'](node, content, this.vm)}} nodeToFragment(node) {// Create document fragmentlet fragment = document.createDocumentFragment()
letFirstChild // Add the template to the document fragmentwhile(firstChild = node.firstChild) {// appendChild can move template elements to document fragments fragment.appendChild(firstChild)}returnFragment} isElementNode(node) {// Check whether it is an element //1. Element node 2. Attribute node 3. Text nodereturn node.nodeType === 1
}
}
Copy the code
Implement Watcher, as a hub, to receive notifications from Observe and perform update methods in the Dep.
Constructor (vm, expr, cb) {this.vm = vm; this.expr = expr; this.cb = cb; // store the oldValue this.oldvalue = this.getvalue ()}getValue() {// When getting the old value, first add yourself to the global dep.target = this; // Watcher instance // To get a value that has been hijacked, the object.defineProperty get method is called to add watcher to the subscriberletNewValue = compierUtil.getValue (this.vm,this.expr);return newValue
}
update() {// get a new valuelet newValue = CompierUtil.getValue(this.vm,this.expr)
if(newValue ! = this.oldValue) {this.cb(newValue)}}Copy the code
Implement Dep: Manage subscribers and notify updates
class Dep{
constructor() {// Store subscriber this.subs = []} // subscribe addSub(watcher) {this.subs.push(watcher)} // publishnotifyUpdate this.subs.foreach (watcher => {watcher.update()})}}Copy the code
Parse instruction method
CompierUtil = {getValue(vm, expr) {school. Name => Alex, message => <h1> lol </h1>return expr.split('. ').reduce((data, current) => {
return data[current]
}, vm.$data)},setValue(vm,expr, Value) {// reassign expr. Split ('. ').reduce((data, current, index, arr) => {
if (index === arr.length -1 ) {
return data[current] = value
}
return data[current]
}, vm.$data}, model(node, expr, vm) {// Define methods to update element contentslet fn = this.updater['modelUpdater'] // Get the value based on the expressionletValue = this.getValue(vm, expr) // Initialize view render fn(node, value) // Subscribe new Watcher(vm, expr) to input box (V-model) (newValue) => {// Data changes are executed to update the view fn(node, newValue)})'input', (e) => {
letnewValue = e.target.value; // Update the new value to the data (vm).$data) this.setValue(vm, expr, newValue)})}, HTML (node,expr,vm) {// Define a method to update the contents of the elementlet fn = this.updater['htmlUpdater'] // Get the value based on the expressionletValue = this.getValue(vm, expr) // Initialize view render fn(node, value) // Subscribe to V-HTML new Watcher(vm, expr, (newValue) => { Fn (node, newValue)})}, on(node,expr,vm,eventName) {// Perform event listener on the element corresponding to the on instruction V-on :click="change"Node.addeventlistener (eventName, (e) => {// expr => change // change this points to vm vm[expr].call(vm,e)})} GetContentValue (vm,expr) {getContentValue(vm,expr) {letvalue = expr.replace(/\{\{(.+?) \}\}/g, (... args) => { // console.log(args) ["{{school.name}}"."school.name", 0, "{{school.name}}{{school.age}}"]
return this.getValue(vm, args[1])
})
returnValue}, text(node, expr, vm) {// Define the method to update the text contentlet fn = this.updater['textUpdater'Expr => {{school.name}}{{school.age}}letcontent = expr.replace(/\{\{(.+?) \}\}/g, (... The args) = > {/ / for each new Watcher {{}} to join observer (vm, args [1], () = > {/ / for each {{}} the element node update fn (node, this.getContentValue(vm,expr) ) })let value = this.getValue(vm, args[1])
returnValue}) // Initialize render fn(node, content)}, updater: ModelUpdater (node, value) {node.value = value}, // Text update method textUpdater(node, value) HtmlUpdater (node,value) {node.innerhtml = value}}Copy the code