I want to illustrate the principle in very simple terms, the first time to understand it, you don’t have to look at the code.
Suppose I have a template:
<div id="app">
<span>{{msg}}</span>
<div v-bind="msg"></div>
<input v-model="msg" />
</div>
Copy the code
Suppose I create a vue instance:
const vm = new Vue({
el: '#app'.data: {
msg: 'Hello Vue'}})Copy the code
Pretend I have a picture:
The data was hijacked
First, Vue gets the data and uses Object.defineProperty to add get and set methods to the MSG in the data. Handling this process is called “observe”
Like this:
class Observer {
constructor (data) {
this.walk(data)
}
walk (data) {
// Determine whether data is an object
if(! data ||typeofdata! = ='object') return
// Iterate over all properties of the object
Object.keys(data).forEach(key= > {
this.defineReactive(data, key, data[key])
})
}
defineReactive (obj, key, val) {
// If val is an object, the interior is also converted to responsive data
this.walk(val)
let _this = this
// Collect dependencies and send notifications
let dep = new Dep()
Object.defineProperty(obj, key, {
enumerable: true.configurable: true,
get () {
// Collect dependencies
Dep.target && dep.addSub(Dep.target)
return val
},
set (newValue) {
if (newValue === val) return
val = newValue
_this.walk(newValue)
// Send notifications
dep.notify()
}
})
}
}
Copy the code
}}
Template compilation
{{MSG}} – This parsing is called “Compiler” template parsing. After updating the value, Compiler tells watcher that I used the value data[MSG]. If it is set, you should give me the updated value and I should change the content.
Like this:
/*** * functions: * Responsible for compiling the last class, parsing instructions/difference expressions * Responsible for the first rendering of the page * Re-rendering the view when the data changes * ****/
class Compiler {
constructor (vm) {
this.el = vm.$el
this.vm = vm
this.compile(this.el)
}
// Compile the last class to handle text nodes and element nodes
compile (el) {
let childNodes = el.childNodes
Array.from(childNodes).forEach(node => {
// Process text nodes
if (this.isTextNode(node)) {
this.compileText(node)
} else if (this.isElementNode(node)) {
// Process element nodes
this.compileElement(node)
}
// Check whether node has child nodes
if(node.childNodes & & node.childNodes.length) {this.compile(node)
}
})
}
// Compile element node, process instruction
compileElement (node) {
// Iterate over all attribute nodes
Array.from(node.attributes).forEach(attr => {
let attrName = attr.name
if (this.isDirective(attrName)) {
attrName = attrName.substr(2)
let key = attr.value
let eventType = ' & #39;
if (attrName.startsWith(' on:') ) { eventType = attrName.slice(3)
attrName = attrName.substr(0.2)}this.update(node, key, attrName, eventType)
}
})
// Check if it is a command
}
update (node, key, attrName, eventType) {
let updateFn = this[attrName + ' Updater' ] updateFn & & updateFn.call(this, node, this.vm[key], key, eventType)
}
// Process the V-text instruction
textUpdater (node, value, key) {
node.textContent = value
new Watcher(this.vm, key, (newValue) => {
node.textContent = newValue
})
}
// Process the V-ON instruction
onUpdater (node, value, key, eventType) {
node.addEventListener(eventType, value.bind(this.vm))
new Watcher(this.vm, key, newValue => {
node.removeEventListener(eventType, value.bind(this.vm))
node.addEventListener(eventType, newValue.bind(this.vm))
})
}
// Handle v-HTML directives
htmlUpdater (node, value, key) {
node.innerHTML = value
new Watcher(this.vm, key, (newValue) => {
node.innerHTML = newValue
})
}
modelUpdater (node, value, key) {
node.value = value
new Watcher(this.vm, key, (newValue) => {
node.value = newValue
})
// Bidirectional binding
node.addEventListener(' input' , () => {this.vm[key] = node.value
})
}
// Compile the text node to handle the difference expression
compileText (node){
let reg = / \ {\ {(. +?) \} \} /
let value = node.textContent
if (reg.test(value)) {
let key = RegExp.$1.trim()
node.textContent = value.replace(reg, this.vm[key])
// Create a Watcher object to update the view when the data changes
new Watcher(this.vm, key, (newValue) => {
node.textContent = newValue
})
}
}
// Determine if element attributes are directives
isDirective (attrName) {
return attrName.startsWith(' v-')
}
// Determine if the element attribute is a text node
isTextNode (node) {
return node.nodeType === 3
}
// The node is an element node
isElementNode (node) {
return node.nodeType === 1}}Copy the code
}
Depend on the collection
Third, the watcher, after receiving the compiler’s action, can wait in data’s set method. But there may be many watcher, and one watcher will be created for every place where data[MSG] is used. You might as well call the Dep publisher and collect the Watcher. Watcher has an update method that executes a span callback whenever the observed value changes. Watcher saves the span callback and retrieves the current MSG value. If you get the MSG value, it will trigger the get method. Tell Dep to wait here and pack up when Watcher arrives.
Like this:
class Watcher {
constructor (vm, key, cb) {
this.vm = vm
this.key = key
// The callback function that updates the view
this.cb = cb
// Record the watcher object to the Dep static attribute target
Dep.target = this
// Trigger the get method, in which addSub is called
this.oldValue = vm[key]
Dep.target = null
}
// Update the view when data changes
update () {
let newValue = this.vm[this.key]
if (this.oldValue === newValue) return
this.cb(newValue)
}
}
Copy the code
Update () {let newValue = this.vm[this.key] if (this.oldValue === newValue) return this.cb(newValue) } }
Distributed update
Fourth, Dep installs all the watchers into its sub (an array) in get. Once the MSG set is called, check to see if the MSG has changed. If the MSG has changed, remove the watcher from each sub and execute the update one by one. The update executes the innerHTML of the span, which is updated with the new value of MSG
class Dep {
constructor () {
// Store all observers
this.subs = []
}
// Add an observer
addSub (sub) {
if (sub && sub.update) {
this.subs.push(sub)
}
}
// Send notifications
notify () {
this.subs.forEach(sub= > {
sub.update()
})
}
}
Copy the code
Call from an entry file:
class Vue {
constructor(options) {
// Save the data in the options via attributes
this.$options = options || {}
this.$data = options.data || {}
this.$el = typeof options.el === 'string' ? document.querySelector(options.el) : options.el
// Convert the data members into getters and setters for the injected vue instance
this._proxyData(this.$data)
// Call an observer to listen for changes in data
new Observer(this.$data)
// Call the Compiler object to parse instructions and differential expressions
new Compiler(this)
}
_proxyData (data) {
// Iterate over all attributes in data
Object.keys(data).forEach(key => {
Object.defineProperty(this, key, {
enumerable: true.configurable: true.get() {
return data[key]
},
set (newValue) {
if (newValue === data[key]) return
data[key] = newValue
}
})
})
}
}
Copy the code
}
The overall process is like this, if the simple model to figure out, and then look at the source ideas will be very clear.