Before talking about the Vue responsive principle, we need to be familiar with the reduce method in the array, and can skillfully use the Reduce () method to solve the needs of the problem, for the following understanding of the Vue responsive principle in advance to heat up the brain. Let’s start by introducing the reduce () method.
Reduce () method
Reduce () : Loops through the current array, and the next operation depends on the last return value, which is equivalent to a “snowball” operation.
Reduce ((result of last calculation, current loop item) => {return result of last calculation + current loop item}, initial value)Copy the code
Application scenario: The initial value of the next operation depends on the return value of the last operation.
Realize the operation of numerical accumulation
When we add up a number, the common thing to think about is iterating through it using forEach ().
const array = [1.2.3.4.5]
let sum = 0
array.forEach(item= > {
sum += item
})
console.log(sum) / / 15
Copy the code
In the summation above, I wrote too many lines of code and needed to declare an external variable to store the value. It seems wordy and cumbersome. However, we can use the reduce () method to quickly implement the numerical accumulation operation.
const array = [1.2.3.4.5]
const sum = array.reduce((pre, next) = > pre + next,0)
console.log(sum) / / 15
Copy the code
Both methods implement summation, but the reduce () method looks much cleaner than the forEach () method.
Implement object chain value operation
The power of the reduce () method is to realize not only the sum operation of the number, but also the operation of the chain value of the object, etc.
const obj = {
name: 'alex'.info: {
address: {
location: 'gz'}}}const array = ['info'.'address'.'location']
const value = array.reduce((pre, next) = > pre[next], obj)
console.log(value) // gz
Copy the code
Chain gets the value of an object property upgrade operation
const obj = {
name: 'alex'.info: {
address: {
location: 'gz'}}}const objInfo = 'info.address.loaction'
const value =objInfo.split('. ').reduce((pre, next) = > pre[next], obj)
console.log(value) // gz
Copy the code
Ok, with the reduce () method above, I believe you are in the mood, and now we are on topic.
Publish and subscribe model
Here’s a simple example: Existing supply Pepsi cola soda shop, at that moment, to the A, B, C three people, is to buy Pepsi, but the Pepsi sold out of the shop, the manager took out A notebook to record respectively their contact information, wait for Pepsi, the arrival of the goods, then take out the book one-to-one inform their three people to take goods, Then A, B, and C each take the Pepsi to do something else. In this example, people A, B, and C can be understood as Watcher subscribers, and the store can be understood as A Dep, which is responsible for collecting dependencies and notifting Watcher, and Watcher can then do related things (such as re-rendering the page or updating the data-driven view) after receiving the notification.
From the simple example above, we also know that the Dep class should have the following functions:
Responsible for dependency collection.
First, there is an array that holds all the subscription information.
Second, you provide an addSub () method that appends a subscription to the array.
Finally, a loop is provided that triggers each subscription message in the array.
The Watcher class is responsible for subscribing to events, mainly a callback function
Vue responsive principle
From the above examples, we have a good understanding of what the publish subscribe model is and what the Dep class and Watcher class can do. Let’s take a closer look at the principle of Vue responsiveness. The core method of the Vue responsivity principle is the hijacking of attributes through Object.defineProperty() to listen for data changes. This method is one of the most important and basic in this article.
To implement Vue’s bidirectional data binding, the following must be implemented:
- To implement a
Dep
, is mainly used to collect dependencies (subscribers) and notify corresponding subscribers to update data. - Implement a data listener
Observer
, can listen on all attributes of the data object, addsetter
, andgetter
Method to notify dependent collection objects of the latest value if there is a change (Dep
) and inform subscribers (Watcher
) to update view changes. - Implement a parser
Compile
, scan and parse the instructions of each element node, replace data according to the instructions, and bind the corresponding update function. - To implement a
Watcher
, as a connectionObserver
åCompile
Is able to subscribe to and receive notifications of each property change, execute the corresponding callback function, and update the view.
Implement the parser Compile
Compile implements a parser to scan and parse instructions of each element node, replace data according to instructions, and bind the corresponding update function.
Initialize the
class MVue {
constructor (options) {
this.$el = options.el,
this.$data = options.data,
this.$options = options
// Start compiling if a template template exists
if (this.$el) {
// create the parser Compile
new Compile(this.$el, this)}}}class Compile {
constructor (el, vm) {
this.vm = vm
// Check if it is an element node
this.el = this.isElementNode(el) ? el : document.querySelector(el)
}
isElementNode (node) {
// node.nodeType = 1 is the element node; nodeType = 3 is the text node
return node.nodeType === 1}}Copy the code
Creating document fragments
Because every time the match is replaced, the page will be backflowed and redrawn, affecting the performance of the page, so it is necessary to create document fragments to cache and reduce the backflowed and redrawn pages.
class MVue {
constructor (options) {
this.$el = options.el,
this.$data = options.data,
this.$options = options
// Start compiling if a template template exists
if (this.$el) {
// create the parser Compile
new Compile(this.$el, this)}}}class Compile {
constructor (el, vm) {
this.vm = vm
// Check if it is an element node
this.el = this.isElementNode(el) ? el : document.querySelector(el)
// Because every time the match is replaced, the page will be backflowed and redrawn, affecting the performance of the page
// Document shards need to be created to cache and reduce page backflow and redraw
console.log(this.el);
const framgent = this.createFramgent(this.el)
// Add the document fragment to the root element and render it to the page
this.el.appendChild(framgent)
}
// Create a document fragment
createFramgent (node) {
const framgent = document.createDocumentFragment(node)
// The loop adds nodes in turn to the document fragment firstChild contains a space newline character
// console.log(node.firstChild);
let children
while (children = node.firstChild) {
// Append in sequence to the document fragment
framgent.appendChild(children)
}
return framgent
}
isElementNode (node) {
// node.nodeType = 1 is the element node; nodeType = 3 is the text node
return node.nodeType === 1}}Copy the code
Compile templates recursively
class MVue {
constructor (options) {
this.$el = options.el,
this.$data = options.data,
this.$options = options
// Start compiling if a template template exists
if (this.$el) {
// create the parser Compile
new Compile(this.$el, this)}}}class Compile {
constructor (el, vm) {
this.vm = vm
// Check if it is an element node
this.el = this.isElementNode(el) ? el : document.querySelector(el)
// Because every time the match is replaced, the page will be backflowed and redrawn, affecting the performance of the page
// Document shards need to be created to cache and reduce page backflow and redraw
console.log(this.el);
const framgent = this.createFramgent(this.el)
// Start compiling the template
this.compile(framgent)
// Add the document fragment to the root element and render it to the page
this.el.appendChild(framgent)
}
compile (framgent) {
const childNodes = framgent.childNodes
console.log(childNodes)
// Walk through all the nodes and determine whether they are element nodes or text nodes
// Convert a pseudo-array to a true array
const childNodesArray = Array.from(childNodes)
childNodesArray.forEach(node= > {
if(this.isElementNode(node)){
// is the element node
console.log(node);
} else {
// is a text node
console.log(node);
}
// Multilevel nesting requires recursive child elements
if(node.childNodes && node.childNodes.length){
this.compile(node)
}
})
}
// Create a document fragment
createFramgent (node) {
const framgent = document.createDocumentFragment(node)
// The loop adds nodes in turn to the document fragment firstChild contains a space newline character
// console.log(node.firstChild);
let children
while (children = node.firstChild) {
// Append in sequence to the document fragment
framgent.appendChild(children)
}
return framgent
}
isElementNode (node) {
// node.nodeType = 1 is the element node; nodeType = 3 is the text node
return node.nodeType === 1}}Copy the code
Parse compiled elements
class MVue {
constructor(options) {
this.$el = options.el,
this.$data = options.data,
this.$options = options
// Start compiling if a template template exists
if (this.$el) {
// create the parser Compile
new Compile(this.$el, this)}}}class Compile {
constructor (el, vm) {
this.vm = vm
// Check if it is an element node
this.el = this.isElementNode(el) ? el : document.querySelector(el)
// Every time the match is replaced, the page will be backflowed and redrawn, affecting the performance of the page
// Document shards need to be created to cache and reduce page backflow and redraw
console.log(this.el);
const framgent = this.createFramgent(this.el)
// Start compiling the template
this.compile(framgent)
// Add the document fragment to the root element and render it to the page
this.el.appendChild(framgent)
}
compile (framgent) {
const childNodes = framgent.childNodes
// console.log(childNodes)
// Walk through all the nodes and determine whether they are element nodes or text nodes
// Convert a pseudo-array to a true array
const childNodesArray = Array.from(childNodes)
childNodesArray.forEach(node= > {
if(this.isElementNode(node)){
// is the element node
// console.log(node);
this.compileElement(node)
} else {
// is a text node
// console.log(node);
this.compileText(node)
}
// Multilevel nesting requires recursive child elements
if(node.childNodes && node.childNodes.length){
this.compile(node)
}
})
}
// Parse the compiled element node
compileElement (elementNode) {
// Compile the element to get the attributes of the element node through attributes, which contain name and value
const attributes = elementNode.attributes;
[...attributes].forEach(attr= > {
// name Attribute name v-text V-html value Attribute value obj.name obj.age
const {name, value} = attr
if (this.isDirective(name)) {
/ / instructions
// Deconstruct v-text v-html
const [,directive] = name.split(The '-')
const [dirName, eventName] = directive.split(':')
// Can Riemann store this instruction in compileUtils
compileUtils[dirName] && compileUtils[dirName](elementNode, value, this.vm, eventName)
// Attributes in the sequence tag
elementNode.removeAttribute('v-' + directive)
} else if (this.isEventName(name)) {
/ / is the event
const [,eventName] = name.split(The '@')
// Process different data according to different instructions text HTML model
compileUtils['on'](elementNode, value, this.vm, eventName)
}
});
}
// Is it a command
isDirective (name) {
// Start with v-
return name.startsWith('v-')}// Whether it is an event
isEventName (name) {
// Start with @
return name.startsWith(The '@')}// Parse and compile text nodes
compileText (textNode) {
// Compile text
}
// Create a document fragment
createFramgent (node) {
const framgent = document.createDocumentFragment(node)
// The loop adds nodes in turn to the document fragment firstChild contains a space newline character
// console.log(node.firstChild);
let children
while (children = node.firstChild) {
// Append in sequence to the document fragment
framgent.appendChild(children)
}
return framgent
}
isElementNode (node) {
// node.nodeType = 1 is the element node; nodeType = 3 is the text node
return node.nodeType === 1}}Copy the code
Parse compiled text
// Parse and compile text nodes
compileText (textNode) {
// Compile text
// Get the text content
const content = textNode.textContent
// Regex matches
const reg = / \ {\ {(. +?) \} \} /
if(reg.test(content)) {
// Process different data according to different instructions text HTML model
compileUtils['text'](textNode, content, this.vm)
}
}
Copy the code
So if you’re wondering what compileUtils are and what they’re supposed to handle, compileUtils is an object that handles different instructions, v-text handles text, V-HTML handles HTML elements, V-model is…. for processing form data There’s a updater in compileUtils that has methods for updating views.
CompileUtils object
const compileUtils = {
// Get the value of the attribute in data
getValue (value, vm) {
// Split. Into an array, and then use reduce to get the attribute values in data
return value.split('. ').reduce((pre, next) = > {
return pre[next]
},vm.$data)
},
text (node , value, vm) { // value may be {{obj.name}} may be obj.age
let val
if (value.indexOf('{{')! = = -1) {
{{obj. Name}}
// Perform global matching
val = value.replace(/ \ {\ {(. +?) \}\}/g.(. args) = > {
return this.getValue(args[1], vm)
})
} else {
// obj.age
val = this.getValue(value, vm)
}
// Update/replace data
this.updater.textUpdata(node, val)
},
html (node, value, vm) {
const val = this.getValue(value, vm)
// Update/replace data
this.updater.htmlUpdata(node,val)
},
model (node, value ,vm) {
const val = this.getValue(value, vm)
this.updater.modleUpdata(node, val)
},
on (node, value, vm, eventName) {
// Get the callback function
let fn = vm.$options.methods && vm.$options.methods[value]
node.addEventListener(eventName, fn.bind(vm), false)},// there is a method for updating the view corresponding to the directive
updater:{
textUpdata (node, value) {
node.textContent = value
},
htmlUpdata (node, value) {
node.innerHTML = value
},
modleUpdata (node, value) {
node.value = value
},
}
}
Copy the code
According to the above flow chart, we have completed the step of new MVVM () -> Compile -> Updater, which implements the initialization of the page view data display. Next we proceed from Observer -> Dep -> Watcher -> Updater.
Implement a listener Observer
The purpose of the Observer is to hijack the listener for each property in the object, set setters and getters, and call notify() in the Dep to notify the subscriber of any changes.
We can use object.defineProperty () to listen for property changes, and use the observe method to recursively traverse the data of the Object, including the property of the child property Object, adding setters and getters. When we assign a value to an Object or get a value, I’m going to fire setter and getter methods, and I’m going to be able to listen for changes in the data.
class Observer{
constructor (data) {
this.observe(data)
}
observe (data) {
// Do not consider array data and data is an object
if (data && typeof data === 'object') {// Iterate over the key of the object
Object.keys(data).forEach(key= > {
// Pass the object key into the value
this.defineReactive(data, key, data[key])
})
}
}
defineReactive (obj, key, value) {
// recursive traversal
this.observe(value)
Object.defineProperty(obj, key, {
configurable: false.enumerable: true,
get () {
return value
},
set: (newValue) = > {
if( newValue ! = value) {// If you assign to an object directly, you also need to listen for data on that object
this.observe(newValue)
value = newValue
// dep.notify Notifies watcher of changes to update the view
dep.notify()
}
}
})
}
}
Copy the code
Depends on the collection object Dep
We can use the above Observer to monitor the data of each object property, and use the notify method of deP to notify the subscriber (Watcher) to perform the callback function to change the view when the data changes.
Dep role:
Create an array to hold subscribers and declare methods to add subscribers (addSub)
Declare a notification subscription method (notify)
class Dep {
constructor () {
/ / store watcher
this.sub = []
}
// Add subscribers
addSub (watcher) {
// Place the subscribers in an array
this.sub.push(watcher)
}
// Notify subscribers
notify () {
// Loop through the watcher bound callback function in turn
this.sub.forEach(watcher= > watcher.update())
}
}
Copy the code
The Watcher subscriber
Watcher acts as a bridge between the Observer and Compile, subscribing to and receiving notification of each property change, and executing the corresponding bound callback function to update the view. Watcher must have three things:
- In their own
Instantiate to add itself to the Dep
. - You have to have one
Update () method
. - Wait for the attribute value to change
Dep. Notify ()
To be able to call its ownUpdate () method
And,Triggers the callback function bound in Compile
.
class Watcher{
constructor (vm, value, callback) {
// The vm holds the latest values
// value Specifies the attribute of the data change
// Bind the callback function
this.vm = vm
this.value = value
this.callback = callback
// Store old values
this.oldValue = this.getOldValue()
}
// The getter is triggered indirectly to get the value
getOldValue () {
Dep.target = this
const olaValue = compileUtils.getValue(this.value, this.vm)
Dep.target = null
return olaValue
}
update () {
Dep notifies the subscriber of changes in operation data through notify and the subscriber updates the view
const newValue = compileUtils.getValue(this.value, this.vm)
if(newValue ! = =this.oldValue) {
this.callback(newValue)
this.oldValue = newValue
}
}
}
Copy the code
At this point, we have written the required Observer, Watcher, Compiler, and Dep. So, all that’s left is how to connect them together to form a loop, and that’s what we’re going to do with the responsive form. When we open the page, the first implementation is Observer first to listen to and hijack the data of each attribute of the object, and then use the Compile parser to parse, the first step: we can make sure that the new Observer and then new Compile
class MVue {
constructor(options) {
this.$el = options.el,
this.$data = options.data,
this.$options = options
// Start compiling if a template template exists
if (this.$el) {
// 1. Data hijacking
new Observer(this.$data)
// create parser Compile
new Compile(this.$el, this)}}}Copy the code
The dep. Notify method is triggered when the data is modified. So we can make sure that new Dep collects dependencies and triggers notify after recursion before Object.defineProperty, And we can trigger the dep.addSub method in the getter to add the Watcher instance to the array, so we have a collection of dependent objects.
class Observer{
constructor (data) {
this.observe(data)
}
observe (data) {
// Do not consider array data and data is an object
if (data && typeof data === 'object') {// Iterate over the key of the object
Object.keys(data).forEach(key= > {
// Pass the object key into the value
this.defineReactive(data, key, data[key])
})
}
}
defineReactive (obj, key, value) {
// recursive traversal
this.observe(value)
const dep = new Dep()
Object.defineProperty(obj, key, {
configurable: false.enumerable: true,
get () {
// Add subscribers to the collection dependency array if there are any
Dep.target && dep.addSub(Dep.target)
return value
},
set: (newValue) = > {
if( newValue ! = value) {// If you assign to an object directly, you also need to listen for data on that object
this.observe(newValue)
value = newValue
// dep.notify Notifies watcher of changes to update the view
dep.notify()
}
}
})
}
}
Copy the code
In the end, all that is left is how Complie and Watcher relate to each other. In Compile, we declare a compileUtils for each instruction. Inside this compileUtils, we declare an updater. It contains data updates for various instructions, such as textUpdate, htmlUpdate, modelUpdate, etc. So, we found the function to update data, we can confirm that before updating the function, new Watcher, The subscriber instance (Watcher) is triggered when the data changes to trigger the bound callback function, updating the view.
const compileUtils = {
// Get the value of the attribute in data
getValue (value, vm) {
// Split. Into an array, and then use reduce to get the attribute values in data
return value.split('. ').reduce((pre, next) = > {
return pre[next]
},vm.$data)
},
// {{obj.name}}-- {{obj.age}} get the value again to avoid changing one and two at the same time
getContentValue (value, vm) {
return value.replace(/ \ {\ {(. +?) \}\}/g.(. args) = > {
return this.getValue(args[1], vm);
})
},
text (node , value, vm) { // value may be {{obj.name}} may be obj.age
let val
if (value.indexOf('{{')! = = -1) {
{{obj. Name}}
// Perform global matching
val = value.replace(/ \ {\ {(. +?) \}\}/g.(. args) = > {
/ /... Args prints the three values of the current match, the smallest match in the string, and the original string
new Watcher(vm, args[1].() = > {
this.updater.textUpdata(node, this.getContentValue(value, vm))
})
return this.getValue(args[1], vm)
})
} else {
// obj.age
val = this.getValue(value, vm)
}
// Update/replace data
this.updater.textUpdata(node, val)
},
html (node, value, vm) {
const val = this.getValue(value, vm)
new Watcher(vm, value, (newValue) = > {
this.updater.htmlUpdata(node, newValue)
})
// Update/replace data
this.updater.htmlUpdata(node,val)
},
model (node, value ,vm) {
const val = this.getValue(value, vm)
new Watcher(vm, value, (newValue) = > {
this.updater.modleUpdata(node, newValue)
})
this.updater.modleUpdata(node, val)
},
on (node, value, vm, eventName) {
// Get the callback function
let fn = vm.$options.methods && vm.$options.methods[value]
node.addEventListener(eventName, fn.bind(vm), false)},updater:{
textUpdata (node, value) {
node.textContent = value
},
htmlUpdata (node, value) {
node.innerHTML = value
},
modleUpdata (node, value) {
node.value = value
},
}
}
Copy the code
After the above analysis, we have completed the update of data change to view, but in the form, the update of view change to data has not been realized, so we still need to analyze the form data.
/ Handle different data according to different instructions text HTML modelconst compileUtils = {
// Get the value of the attribute in data
getValue (value, vm) {
// Split. Into an array, and then use reduce to get the attribute values in data
return value.split('. ').reduce((pre, next) = > {
return pre[next]
},vm.$data)
},
getContentValue (value, vm) {
return value.replace(/ \ {\ {(. +?) \}\}/g.(. args) = > {
return this.getValue(args[1], vm); })},// View -> Data
setValue (vm,value,inputValue) {
return value.split('. ').reduce((pre, next) = > {
if (typeofpre[next] ! = ='object') {
// If it is not an object, it is directly assigned
pre[next] = inputValue
}
// The value of the object is direct
return pre[next]
},vm.$data)
},
text (node , value, vm) { // value may be {{obj.name}} may be obj.age
let val
if (value.indexOf('{{')! = = -1) {
{{obj. Name}}
// Perform global matching
val = value.replace(/ \ {\ {(. +?) \}\}/g.(. args) = > {
/ /... Args prints the three values of the current match, the smallest match in the string, and the original string
new Watcher(vm, args[1].() = > {
this.updater.textUpdata(node, this.getContentValue(value, vm))
})
return this.getValue(args[1], vm)
})
} else {
// obj.age
val = this.getValue(value, vm)
}
// Update/replace data
this.updater.textUpdata(node, val)
},
html (node, value, vm) {
const val = this.getValue(value, vm)
new Watcher(vm, value, (newValue) = > {
this.updater.htmlUpdata(node, newValue)
})
// Update/replace data
this.updater.htmlUpdata(node,val)
},
model (node, value ,vm) {
const val = this.getValue(value, vm)
// Data update -> View changes
new Watcher(vm, value, (newValue) = > {
this.updater.modleUpdata(node, newValue)
})
// View changes -> Data updates
node.addEventListener('input'.(e) = > {
this.setValue(vm, value, e.target.value)
} ,false)
this.updater.modleUpdata(node, val)
},
on (node, value, vm, eventName) {
// Get the callback function
let fn = vm.$options.methods && vm.$options.methods[value]
node.addEventListener(eventName, fn.bind(vm), false)},updater:{
textUpdata (node, value) {
node.textContent = value
},
htmlUpdata (node, value) {
node.innerHTML = value
},
modleUpdata (node, value) {
node.value = value
},
}
}
class Compile {
constructor (el, vm) {
this.vm = vm
// Check if it is an element node
this.el = this.isElementNode(el) ? el : document.querySelector(el)
// Every time the match is replaced, the page will be backflowed and redrawn, affecting the performance of the page
// Document shards need to be created to cache and reduce page backflow and redraw
console.log(this.el);
let framgent = this.createFramgent(this.el)
// Start compiling the template
this.compile(framgent)
// Add the document fragment to the root element and render it to the page
this.el.appendChild(framgent)
}
compile (framgent) {
const nodes = framgent.childNodes;
// console.log(childNodes)
// Walk through all the nodes and determine whether they are element nodes or text nodes
// Convert a pseudo-array to a true array
// const childNodesArray = Array.from(childNodes)
[...nodes].forEach(node= > {
if(this.isElementNode(node)){
// is the element node
// console.log(node);
this.compileElement(node)
} else {
// is a text node
// console.log(node);
this.compileText(node)
}
// Multilevel nesting requires recursive child elements
if(node.childNodes && node.childNodes.length){
this.compile(node)
}
})
}
// Parse the compiled element node
compileElement (elementNode) {
// Compile the element to get the attributes of the element node through attributes, which contain name and value
const attributes = elementNode.attributes;
[...attributes].forEach(attr= > {
// name Attribute name v-text V-html value Attribute value obj.name obj.age
const {name, value} = attr
if (this.isDirective(name)) {
/ / instructions
// Deconstruct v-text v-html
const [,directive] = name.split(The '-')
const [dirName, eventName] = directive.split(':')
// If there is a function corresponding to this instruction
compileUtils[dirName] && compileUtils[dirName](elementNode, value, this.vm, eventName)
// Attributes in the sequence tag
elementNode.removeAttribute('v-' + directive)
} else if (this.isEventName(name)) {
/ / is the event
const [,eventName] = name.split(The '@')
compileUtils['on'](elementNode, value, this.vm, eventName)
}
});
}
// Is it a command
isDirective (name) {
// Start with v-
return name.startsWith('v-')}// Whether it is an event
isEventName (name) {
// Start with @
return name.startsWith(The '@')}// Parse and compile text nodes
compileText (textNode) {
// Compile text
// Get the text content
const content = textNode.textContent
// Regex matches
const reg = / \ {\ {(. +?) \} \} /
if(reg.test(content)) {
compileUtils['text'](textNode, content, this.vm)
}
}
// Create a document fragment
createFramgent (node) {
const framgent = document.createDocumentFragment(node)
// The loop adds nodes in turn to the document fragment firstChild contains a space newline character
// console.log(node.firstChild);
let children
while (children = node.firstChild) {
// Append in sequence to the document fragment
framgent.appendChild(children)
}
return framgent
}
isElementNode (node) {
// node.nodeType = 1 is the element node; nodeType = 3 is the text node
return node.nodeType === 1}}class MVue {
constructor(options) {
this.$el = options.el,
this.$data = options.data,
this.$options = options
// Start compiling if a template template exists
if (this.$el) {
// 1. Data hijacking
new Observer(this.$data)
// create parser Compile
new Compile(this.$el, this)}}}Copy the code
Data proxy
In vue, we can directly use vm. MSG to get the data. In fact, it is the internal proxy for the data for us, equivalent to vm.$data. MSG, so we also need to do data proxy.
class MVue {
constructor(options) {
this.$el = options.el,
this.$data = options.data,
this.$options = options
// Start compiling if a template template exists
if (this.$el) {
// 1. Data hijacking
new Observer(this.$data)
// Data broker
this.proxy(this.$data)
// create parser Compile
new Compile(this.$el, this)}}// Data broker
proxy(data) {
for (const key in data) {
Object.defineProperty(this, key, {
get () {
return data[key]
},
set (newValue) {
data[key] = newValue
}
})
}
}
}
Copy the code
Sort out the whole process of Vue response
- When initializing a Vue instance,
Observer
Will iterate over all attributes in data, usingObject.defineproperty () method
Convert all of these properties togetter/setter
. And create a dependency collection objectdep
A Dep instance for an attribute is used to manage all watchers under that attribute, or create multiple Watchers if the same attribute is used multiple times in a DOM node. - Create when parsing instructions
Watcher instance
And thenThe updated function is placed on the callback of the Watcher instance
. - When the view is initialized, it reads the property value and fires
getter
To createAdd the Watcher instance to the DEP array
. - Trigger when data is modified
setter
, the callDep. Notify method
Notifies the internal dePAll Wacther callback functions are executed
Again,render
The current component generates a new virtual DOM tree. - The Vue framework will be used
The diff algorithm
The differences of each node in the new virtual DOM tree and the old virtual DOM tree are traversed and compared, and recorded. Finally, the loading operation partially modifies the recorded differences to the real DOM tree.
Interview Answer terms
Talk about your understanding of vUE’s MVVM responsive principle.
Vue adopts data hijacking combined with publishe-subscribe mode. It hijacks the getter and setter of each attribute through Object.defineProperty (), publishes messages to subscribers when data changes, and then triggers corresponding listener callback functions to update the view.
The Observer is required to recursively traverse the data, including the attributes of subobjects, adding getters and setters. When reading values or modifying data, getters or setters are triggered and data changes can be monitored.
Compile parses the instructions, initializes the page to replace the variables in the template with data, and binds the updated callback function to the node corresponding to each instruction, adds subscribers, once the data changes, subscribers are notified, triggering the callback to update the view.
Watcher is a bridge between the Observer and Compile. First, it needs to add itself to the deP in its own instance. Second, it needs to update the deP with an update method. Triggers the callback function bound in Compile.
MVVM, as the entry of data binding, integrates Observer, Compile and Watcher, uses Observer to monitor its model data changes, and uses Compile to parse instructions. Finally, Watcher is used to build a communication bridge between Observer and Compile to achieve data change -> view update; View Interactive Changes (INPUT) -> Bidirectional binding effect of data model updates.
That’s all about Vue responsiveness. To obtain the source code, click the link below.
Get source code stamp me!!