The target
Master the basic principle of Vue2 framework
The main points of
I don’t want to talk too much. Just disk it
Let’s start with an HTML file
<div id="app">
<p>{{counter}}</p>
<p v-text="counter"></p>
</div>
<script src="./MyVue.js"></script>
<script>
const app = new XuSirVue({
el: '#app'.data: {
counter: 1}})setInterval(() = > {
app.counter++
}, 1000)
</script>
Copy the code
The key came, I darling, first look at the MVVM classic schematic 😎😎😎
Let’s start with a response form, from the shallow to the deep, continue to look down at 缟
function defineReactive(obj, key, val) {
// Recursive processing, to the object deep interception
observe(val)
// Create a Dep instance
const dep = new Dep()
Object.defineProperty(obj, key, {
get() {
// Check whether dep. target exists and collect dependencies if it does
// Dep.target = watcher
Dep.target && dep.addDep(Dep.target);
return val
},
set(v) {
if(v ! == val) { val = v }// DeP managed Watchers is notified to update when a value change is detected
// this.deps.forEach(dep => dep.update());
Call (this.vm, this.vm[this.key]); // Watcher executes the update function this.updatefn. call(this.vm, this.vm[this.key]); Correspond to the following consents
dep.notify()
}
})
}
defineReactive
function observe(obj) {
// First determine if obj is an object, ignoring my questionable notation 😂
if (typeofobj ! = ='object' || obj === null) {
return
}
new Observer(obj) // Observer
}
Copy the code
Get an observer to listen for changes in data and make it a responsive streak
// Observer: listen for incoming data --> become responsive data
class Observer {
constructor(obj) {
this.value = obj
if (Array.isArray(obj)) {
/ / todo array rewrite the seven methods of short duration does not handle push/shift/pop/unshift...
} else {
this.work(obj)
}
}
// Add intercepts to objects to make them responsive
work(obj) {
Object.keys(obj).forEach(key= > {
defineReactive(obj, key, obj[key])
})
}
}
Copy the code
Insert a proxy — put the key of data on the VM. We can write this. XXX to get variables in data directly without writing this.data
// Exception handling is ignored -- for example, if the VM already has a $data[key], we cannot override the original attribute key
function proxy(vm) {
Object.keys(vm.$data).forEach(key= > {
Object.defineProperty(vm, key, {
get() {
return vm.$data[key];
},
set(newVal){ vm.$data[key] = newVal; }}); })}Copy the code
Vue main entrance came to 缟
class XuSirVue {
constructor(options) {
// 1
this.$options = options
// bind data to $data
this.$data = options.data
observe(this.$data) // Start viewing all data attributes
// proxy - pass vm to proxy method for processing
proxy(this)
/ / 2. com running the compilation
// compile {{}} variables, v-xx instructions into the corresponding values and methods on the binding
new Compile(options.el, this)}}Copy the code
Get a compiler – a bit long but very clear comments, ha ha 😂 缟
class Compile {
constructor(el, vm) {
this.$vm = vm;
this.$el = document.querySelector(el);
/ / traverse el
if (this.$el) {
this.compile(this.$el); }}compile(el) {
const childNodes = el.childNodes;
// childNodes is a pseudo-array
Array.from(childNodes).forEach(node= > {
if (this.isElement(node)) {
console.log("Compile element" + node.nodeName);
this.compileElement(node)
} else if (this.isInterpolation(node)) {
console.log("Compile interpolated text V-text" + node.textContent);
this.compileText(node);
}
if (node.childNodes && node.childNodes.length > 0) {
this.compile(node); }}); }/ / element
isElement(node) {
return node.nodeType == 1;
}
// Is a {{}} interpolation
isInterpolation(node) {
return node.nodeType == 3 && / \ {\ {(. *) \} \} /.test(node.textContent);
}
// use RegExp.$1 to get the interpolation property XXX in {{XXX}}
compileText(node) {
console.log(RegExp. $1);// node.textContent = this.$vm[RegExp.$1];
this.update(node, RegExp. $1,'text')}// Get the instruction expression v-xxx
isDirective(attr) {
return attr.indexOf("v-") = =0;
}
// Compile the element
compileElement(node) {
let nodeAttrs = node.attributes;
// Compile the attribute v-if v-xxx on the element
Array.from(nodeAttrs).forEach(attr= > {
let attrName = attr.name;
let exp = attr.value;
if (this.isDirective(attrName)) {
let dir = attrName.substring(2);
this[dir] && this[dir](node, exp); }}); }// v-text
text(node, exp) {
this.update(node, exp, 'text')}// v-html
html(node, exp) {
this.update(node, exp, 'html')}// Unified processing instruction v-
// dir: text exp: {{XXX}} XXX node: the element node that uses the instruction
update(node, exp, dir) {
const fn = this[dir + 'Updater']
fn && fn(node, this.$vm[exp])
// Triggers a view update
new Watcher(this.$vm, exp, function (val) {
fn && fn(node, val)
})
}
// Handle node assignments for V-text
textUpdater(node, val) {
node.textContent = val;
}
// Handle v-HTML node assignments
htmlUpdater(node, val) {
node.innerHTML = val
}
}
Copy the code
Look tired? Come to a bottle of Red Bull to continue to plate it
Dep is used to rely on collecting attributes in the main listening data. One attribute corresponds to a DEP that listens for changes and updates consents through watcher observers
class Dep {
constructor() {
// Depend on the collection
this.deps = []
}
// Collect all dependencies, one data for each DEP manager, when the getter in the intercepting method above fires
addDep(dep) {
this.deps.push(dep)
}
// A change in the value of the data property triggers the setter, notifying the view to update
notify() {
this.deps.forEach(dep= >dep.update()); }}Copy the code
When the watcher is created, the getter is triggered. When the watcher is initialized, the corresponding data[key] is rendered to the view. Later, the butler DEP intercepts the data[key] when the value changes and updates it in batches. {{initStatus}} v-text=”initStatus”, initStatus corresponds to one DEP manager and two Watcher, InitStatus changes the notify method that triggers the DEP by watcher performing update to finally make the page view update 💘 缟
class Watcher {
constructor(vm, key, updateFn) {
this.vm = vm;
/ / rely on the key
this.key = key;
// Update function
this.updateFn = updateFn;
// Get the key to trigger get and create a mapping between the current Watcher instance and deP
Dep.target = this;
// The purpose of this step is to initialize the value to trigger the getter
this.vm[this.key];
Dep.target = null;
}
/ / update
update() {
this.updateFn.call(this.vm, this.vm[this.key]); }}Copy the code
See the result 😇🤓🤡
supplement
✔ If you find the above code too messy, go to my Github for the full version