preface
With the hot development of Vue, more and more programmers are not satisfied with the use of the framework, more to pursue its internal principle, just like can not sink into the beautiful appearance, should pursue the height of the soul.
The body of the
Well, without further ado, we’re going to pursue our external, nay, internal, pursuits in two ways.
1 Understand the principle of vUE bidirectional data binding
2 after understanding the principle, the interesting soul for a wave of shaping, simple implementation of an MVVM framework
Vue implements bidirectional data binding
Vue. js adopts the mode of data hijacking combined with the observer mode. Through Object.defineProperty(), it hijacks the setter and getter of each attribute, releases the message to the observer when the data changes, and triggers the corresponding listener callback.
Create an interesting soul
Now that we know about vUE bidirectional data, we can implement a simple MVVM framework. Now that we know that VUE is implemented through data hijacking combined with the observer pattern, we can use:
-
1, implement a data listener Observer, can monitor all attributes of the data object, if there is any change can get the latest value and notify the Observer
-
2. Compile an instruction parser, scan and parse the instructions of each element node, replace data according to the instruction template, and bind the corresponding update function
-
Implement a Watcher that acts as a bridge between the Observer and Compile. It receives notification of each property change and executes the corresponding callback function bound by the instruction to update the view
-
4, implement an MVVM entry class, data hijacking entry
1 implementation of an MVVM entry class to achieve the first entry class, unified entry, and then through the acquisition of data for data hijacking and instruction parsing
class mVue {
constructor (options) {
this.$el = options.el;
this.$data = options.data;
this.$options = options;
if (this.$el) {
// 1. Implement a data observer
new Observer(this.$data);
// 2. Implement an instruction parser
new Compile(this.$el, this); }}}Copy the code
Implement a data listener Observer class
Instantiate the Observer class, retrieve the value of each Object of data by recursively calling the Observe method, and hijack the get/set of data via Object.defineProperty. The defineReactive method creates a data dependent Dep as a closure, maintains a Dep for each property, records its own observer (that is, watcher), and notify notifies each observer to perform the corresponding update method to update the view
So the question is, right? How does the Observer class record its own Observer watcher?
Simply maintain an array subs that collects the watcher, triggers notify, and calls the update method of the observer
class Observer{
constructor (data) {
this.observe(data);
}
observe (data) {
/ * * {persion: {name: 'fanke fav: {a:' ball '}}} * /
if(data && typeof data === 'object') {
// Data is hijacked
Object.keys(data).forEach( key= > {
this.defineReactive(data, key, data[key])
})
}
}
defineReactive (data, key, value) {
const _this = this;
// recursive traversal
this.observe(value);
const dep = new Dep(); // The data dependency maintains a Dep for each property to record its own observer (i.e. watcher), and notify each observer to perform the corresponding update method to update the view
Object.defineProperty(data, key, {
enumerable: true./ / can be enumerated
configurable: false.// cannot define again
get() {
// Add an observer to the Dep when data changes
Dep.target && dep.addSub(Dep.target)
return value
},
set (newValue) {
_this.observe(newValue);
if(newValue ! == value) { value = newValue;// Notifies the data dependent DEP, which notifies the observer of the updatedep.notify(); }})}}Copy the code
class Dep {
// Collect + notification
constructor () {
this.subs = [];
}
// Collect observers
addSub (watcher) {
this.subs.push(watcher);
}
// Tell the observer to update
notify () {
console.log('Notify observer'.this.subs)
this.subs.forEach( w= >w.update()); }}Copy the code
3. Implement a Watcher Watcher Observer as a bridge between the Observer and Compile.
A. Add yourself to the property observer (DEP) during self instantiation
B. Must have an update() method of its own
C, when dep.notify() is notified, it can call its own update() method and trigger the callback function cb bound in Compile
/ / observer
class Watcher {
constructor (vm, expr, cb) {
this.vm = vm;
this.expr = expr;
this.cb = cb;
// Get the old value
this.oldVal = this.getOldVal();
}
getOldVal () {
Dep.target = this;
const oldVal = compileUtil.getValue(this.expr, this.vm);
Dep.target = null;
return oldVal;
}
update () {
// Determine whether the new value and the old value have changed
const newVal = compileUtil.getValue(this.expr, this.vm);
if(newVal ! = =this.oldVal) {
this.cb(newVal)
}
}
}
Copy the code
Compile Compile mainly does things is to parse template instructions (such as V-HTML, V-text), replace variables in the template with data, and then initialize render page view, and bind the node corresponding to each instruction update function, add the observer to listen to the data, once the data changes, You are notified and the view is updated
class Compile {
constructor(el, vm) {
this.el = this.isElementNode(el) ? el : document.querySelector(el);
this.vm = vm;
// Get the document fragment object, put it in memory to reduce page backflow and redraw
const fragment = this.node2Fragment(this.el);
// console.log(fragment)
// Compile the template
this.compile(fragment);
// Appends the child element to the root element
this.el.appendChild(fragment);
}
compile (fragment) {
// 1 Gets the child node
const childNodes = fragment.childNodes;
[...childNodes].forEach( child= > {
// console.log(child);
if (this.isElementNode(child)) {
// is the element node
// Compile the element node
// console.log(' element node ', child)
this.compileElement(child)
}else {
// Text node
// console.log(' text node ', child)
this.compileText(child)
}
if (child.childNodes && child.childNodes.length) {
this.compile(child);
}
})
}
compileElement (node) {
// <div v-text="msg"></div>
const attributes = node.attributes;
// console.log(attributes);
[...attributes].forEach(attr= > {
const {name, value} = attr; // name=v-text | value = msg || name=@click value=handleClick
// console.log(name);
if (this.isDirective(name)) { // is the instruction v-text v-model v-html V-on :click
const [, directive] = name.split("-"); // text, model, html, on:click
const [dirName, eventName] = directive.split(":"); // dirName -> text, model, html, on
// Update data data-driven view
compileUtil[dirName](node, value, this.vm, eventName);
// Remove attributes from tags with directives
node.removeAttribute('v-' + directive)
}else if(this.isEventDirective(name)){ // Check whether it is a listening event such as @
const [, eventName] = name.split("@"); // eventDirective = click
compileUtil['on'](node, value, this.vm, eventName);
// Remove attributes from tags with directives
node.removeAttribute(The '@' + eventName)
}
})
}
compileText (node) {
/ / compile {{}}
const content = node.textContent;
// console.log("content", content);
if(/ \ {\ {(. +?) \} \} /.test(content)){
// console.log(content);
compileUtil['text'](node, content, this.vm); }}// Check if it is a Vue directive
isDirective (attrName) {
return attrName.startsWith("v-")}// Determine if the command is an event
isEventDirective (attrName) {
return attrName.startsWith("@")}// Check whether it is a node
isElementNode (node) {
return node.nodeType === 1;
}
node2Fragment (el) {
// Create a document shard object
const f = document.createDocumentFragment();
let firstChild;
while (firstChild = el.firstChild) {
f.append(firstChild);
}
returnf; }}Copy the code
conclusion
Ok, so we probably shaped an interesting soul, here we mainly on the principle of bidirectional data binding, but for the template update is just a simple DOM operation, in fact in the Vue source code is through virtual VDOM + DIff algorithm combined with update queue to achieve, small partners interested in words, Can be viewed in depth Vue source code.
At this time, some students may have some interesting questions, such as:
1 Why is the publish-subscribe model so similar to the Observer model?
Here are the differences:
A In the observer mode, there are only two roles — observer + observed, and there is a third intermediary in the publish/subscribe mode
B The observer and the observed are loosely coupled. Publishers and subscribers, there is no coupling at all
2 How to view the complete code? Please jab here