My bronze version of vUE code address:GitHub | Yards cloud】
Cloud 】 【 making | yards – bronze version of vue code are TB vue source simplified annotation in detail can taste the rest assured
Implementation schematic diagram:
Vue.js initialization flowchart:Corresponding vUE source code
Data Responsive Observer principle:
Observer Role: PassedObject.defineProperty
todata
All levels of data in the
class Observer {
constructor(data) {
//__ob__ A reactive flag that 'inherits' the current this from the object or array that needs the response
Object.defineProperty(data, '__ob__', {
value: this./ / points to this
enumerable: false.// Not enumerable
configurable: false
})
// Determine the array response
if (Array.isArray(data)) {
data.__proto__ = arrayMethods // Replace the encapsulated prototype method
this.observeArray(DataCue)
} else {
this.walk(data)
}
}
observeArray(data) {
for (let i = 0; i < data.length; i++) {
observe(data[i])
}
}
walk(data) {
Object.keys(data).forEach(key= > {
this.defineReactive(data, key, data[key])
})
}
defineReactive(data, key, value) {
observe(value) // Recurse all data responses
let dep = new Dep // One for each attribute
Object.defineProperty(data, key, {
get() {
if (Dep.target) { // Assign dep. target and call get to add a wacher to the property
dep.depend() / / add a watcher
}
return value
},
set(newValue) {
if (newValue === value) return
observe(newValue) // Give the new data response
value = newValue
// View update
dep.notify()
}
})
}
}
export function observe(data) {
// Not an object or =null not monitored
if(! isObject(data)) {return
}
// The monitored object is displayed
if (data.__ob__ instanceof Observer) {
return
}
return new Observer(data)
}
Copy the code
Add get and set methods and extract the hijacked logic separately into defineReactive method. Observe method verifies data type. When the requirement is met, the Observer method is called for property responsiveness, and then the object is recycled for each property to be hijacked. When the data is an array, Observe the observe in the set method to deeply hijack the newly assigned object and ensure that the inserted data is converted into a response. In the defineReactive method, an instance of Dep is created for each attribute, a watcher is added to the attribute Dep when {{data}} reactive data appears on the page, and get is triggered when the render function executes, In get, you can add the watcher to the SUBs array of the Dep for uniform management. Because there are a lot of operations in the code to get the value of data, it will often trigger get, and we need to ensure that the Watcher will not be added repeatedly, so in the Watcher class, After getting the old value and saving it, dep. target is immediately assigned to null, and dep. target is short-circuited when GET is triggered, Dep and Watcher are many-to-many. The logic of one diff per component is one Watcher per component. That is, multiple responsive properties within a component page point to a Watcher and each attribute corresponds to a Dep, The DEP stores more than one Watcher, that is, the DEP appears in more than one Watcher, indicating that the property exists in multiple components page responsive display details please asynchronous source code
Principle of template compiler:
If you get the outerHTML of a node using document.querySelector(“div”).outerhtml you’ll see that it’s the tag code string that you wrote in your HTML file
- The first step we need to pass
parseHTML
Methods theouterHTML
Converted toast
The tree - Step 2 we need to convert the AST tree to
render
The string form of the function - And finally we go through
new Function()
The Render method converts the string form of the render function to the real onerender
Method,render
The function of a method is to generatevNode
Finally passeddiff
Algorithm comparison of new and oldvNode
Thus completed the page update rendering
export function compileToFunctions(template) {
//1. Convert outerHTML to an AST tree
let ast = parseHTML(template) // { tag: 'div', attrs, parent, type, children: [...] }
// console.log("AST:", ast)
//2. Ast tree => Concatenate strings
let code = generate(ast) //return _c('div',{id:app,style:{color:red}}, ... children)
code = `with(this){ \r\n return ${code} \r\n }`
// console.log("code:", code)
//3. String => Executable method
let render = new Function(code)
/ * * is as follows: * render(){ * with(this){ * return _c('div',{id:app,style:{color:red}},_c('span',undefined,_v("helloworld"+_s(msg)) )) *} *} * */
return render
OuterHTML => Ast tree * 2. ast tree => render string * 3. render string => render method */
}
Copy the code
Implementation principle of Patch.js Diff algorithm:
By comparing old and newvNode
Update the DOM render page differentlyWhat is a Vnode? Here is a prototype of a Vnode: el
: is the actual node on the DOM corresponding to the current nodevNode
If not, go straight throughel
Manipulate the actual DOM rendering page it corresponds toHTML
As follows:
<div id="app">
<h1 class="h1">Title 1 Name: {{name}}</h1>
<h2 style="color: red;">Title 2 Age: {{age}}</h2>
<div>
<h3 class="h2">The title three</h3>
<span style="color: pink; font-size: 30px;">Name: {{name}} age: {{age}}</span>
</div>
</div>
Copy the code
First, there are three possible tree-level comparisons: add, delete and change.new VNode
Delete it if it doesn’t existold VNode
If it does not exist, it increases Execute diff to perform update if both exist:
// Diff algorithm core vnode comparison to obtain the final DOM
SRC \core\vdom\patch.js 424 lines
oldStartIndex // Old start index
oldStartVnode // Old beginning
oldEndIndex // Old tail index
oldEndVnode // Get the last of the old children
newStartIndex // Old start index
newStartVnode // Old beginning
newEndIndex // Old tail index
newEndVnode // Get the last of the old children
while (oldStartIndex <= oldEndIndex && newStartIndex <= newEndIndex) {
//1 and 2 resolve the problem of setting nodes to null when arrays collapse
//1. Check whether the old start node exists
if(! oldStartVnode) { oldStartVnode = oldChildren[++oldStartIndex]//2. Check whether the old end node exists
} else if(! oldEndVnode) { oldEndVnode = oldChildren[--oldEndIndex]//3. Whether the old and new start nodes are the same is the recursive patch comparison child node
} else if (isSameVnode(oldStartVnode, newStartVnode)) {
patch(oldStartVnode, newStartVnode)
oldStartVnode = oldChildren[++oldStartIndex]
newStartVnode = newChildren[++newStartIndex]
//4. Whether the old and new end nodes are the same is the recursive patch comparison child node
} else if (isSameVnode(oldEndVnode, newEndVnode)) {
patch(oldEndVnode, newEndVnode);
oldEndVnode = oldChildren[--oldEndIndex]; // Move the tail pointer
newEndVnode = newChildren[--newEndIndex];
//5. Whether the old start node and the new end node are the same is the recursive patch comparison child node
} else if (isSameVnode(oldStartVnode, newEndVnode)) { // Reverst sort
// Move the head and tail in reverse order
patch(oldStartVnode, newEndVnode);
parent.insertBefore(oldStartVnode.el, oldEndVnode.el.nextSibling); // Be mobile
oldStartVnode = oldChildren[++oldStartIndex];
newEndVnode = newChildren[--newEndIndex];
//6. Whether the old end and new start nodes are the same is a recursive patch comparison child node
} else if (isSameVnode(oldEndVnode, newStartVnode)) { // The old tail is compared with the new head
patch(oldEndVnode, newStartVnode)
parent.insertBefore(oldEndVnode.el, oldStartVnode.el)
oldEndVnode = oldChildren[--oldEndIndex]
newStartVnode = newChildren[++newStartIndex]
}
...
Copy the code
Watcher asynchronous update queue principle:
Event Loop:The browser’s work mechanism for coordinating tasks such as event handling, script execution, network requests, and rendering.Macro Task Task:Stands for discrete, independent units of work. The browser completes one macro task and rerenders the page before the next macro task starts. It mainly includes creating document objects and parsingHTML
, execution main lineJS
Code and various events such asPage load
,The input
,Network events
andThe timer
And so on.
Microtasks: Microtasks are smaller tasks that are executed immediately after the execution of the current macro task. If there are any microtasks, the viewer will clear them and re-render them. Examples of microtasks are Promise callbacks, DOM changes, and so on. Experience the process of macro and micro tasks
Asynchronous: As long as it listens for data changes, Vue opens a queue and buffers all data changes that occur in the same event loop. Batch: If the same watcher is triggered more than once, it will only be pushed to the queue once. De-duplication is important to avoid unnecessary computation and DOM manipulation. Then, in the next event loop, “TICK,” Vue refreshes the queue to do the actual work. Asynchronous strategy: Vue internally attempts to use native Promise.then, MutationObserver, or setImmediate for asynchronous queues, and setTimeout instead if none of the execution environments support it.
let has = {}; // Vue source code sometimes to reuse is set sometimes with the object to achieve the de-duplication
let queue = [];
// Whether the queue is waiting for an update
function flushSchedulerQueue() {
for (let i = 0; i < queue.length; i++) {
queue[i].run() // Execute the updateComponent method inside Watcher to update the page
}
queue = []
has = {}
}
// Since multiple elements refer to the same watcher, we need to cluster the watcher together to execute the update
// Reason: If you execute a Watcher for every element that matches, the performance of many of the same Watcher execution is significantly reduced
export function queueWatcher(watcher) {
const id = watcher.id;
if (has[id] == null) {
has[id] = true // If the watcher has not been registered, register the watcher to the queue and mark it as registered
queue.push(watcher) // Watcher stores the updateComponent method to update the page
console.log("queuequeue---", queue);
nextTick(flushSchedulerQueue); // flushSchedulerQueue calls render watcher}}// 1. Callbacks [0] is the dep.notify() method of the flushSchedulerQueue function that listens for data changes in the component
// the 2.dep.notify () method passes all triggered watcher to queueWatcher
FlushCallbacksQueue works in flushCallbacksQueue when the queueWatcher method is used to flush the watcher
let callbacks = []; // [flushSchedulerQueue,fn]
let pending = false
function flushCallbacksQueue() {
callbacks.forEach(fn= > fn())
pending = false
}
// The first time nextTick is entered, a timer is started to execute the nextTick callback function
// Since the JS timer is a macro task, the timer will wait until all the micro tasks have been executed before executing the timer
// So the flushCallbacksQueue method is triggered when the nextTick callbacks inside the component are added one by one and the page is completely rendered
export function nextTick(fn) {
callbacks.push(fn) / / image stabilization
if(! pending) {// The concept of true event loops promise mutationObserver setTimeout setImmediate
setTimeout(() = > {
flushCallbacksQueue() // Clear callback queue
}, 0)
pending = true}}Copy the code