Do not stop. Analyze vuE2
This article is suitable for those who have a certain understanding of VUE framework. If you have not used it before and are not clear about it, please look at the official website of VUE and then read the article. The idea will be much clearer.
Vue source address: github.com/vuejs/vue
It is best that we clone this source code to facilitate our later reading, testing.
Source structure
- Benchmarks compare it to other competing products
- Dist Packaged file
- Examples Examples
- Flow Because Vue uses flow for static type checking, the definition here declares some static types
- Packages vue can also generate other NPM packages separately
- scripts
- SRC Specifies the location of the main source code
- Compiler Files related to template parsing
- Codegen generates the render function from the AST
- The common directives are directives that need to be processed before the render function is generated
- Parser Template parsing
- Core code
- Components global component, here only keep-alive
- Global – related API global method, which is added to the Vue method on the object, such as the Vue. The use of Vue. The extend of Vue. Mixin, etc
- Instance initialization related, including instance methods, lifecycle, events, etc
- Observer Bidirectional data binding files
- Util Tool method
- Vdom Related to the virtual DOM
- Index.js entry file
- Platforms related content
- Web Files unique to the Web
- Compiler The instructions and modules that need to be processed during the compilation phase
- Components, directives, and modules that need to be processed during the Runtime phase
- Server Server side rendering
- Util tool library
- Weex This is a file only on the WEEx side
- Web Files unique to the Web
- Server Server side rendering
Call the vue
First, let’s look at some vUE code
var app = new Vue({
el: '#app'.data: {
message: 'Hello Vue! '}})Copy the code
So the first thing we see in this code is that vue is a constructor and we instantiate it with the new keyword, and then we pass in some parameters. Let’s look for the constructor declared there in the source code.
src\core\instance\index.js
function Vue (options) {
if(process.env.NODE_ENV ! = ='production' &&
!(this instanceof Vue)
) {
warn('Vue is a constructor and should be called with the `new` keyword')}this._init(options)
}
initMixin(Vue) // Init method is defined
stateMixin(Vue) // Initialize the data
eventsMixin(Vue) // Enter the initial events on emit
lifecycleMixin(Vue) // Define the lifecycle
renderMixin(Vue) / / define the render
// We can see from this code that this is a constructor that takes an options argument and executes the init method. Let's see
// The _init method is provided in initMixin
Copy the code
src\core\instance\init.js
// The init method also accepts the options that were originally passed in
Vue.prototype._init = function (options? :Object) {
const vm: Component = this
// a uid
vm._uid = uid++ // Add a unique id to each instance every time new vue +1
vm._isVue = true // Mark the current instance
if (options && options._isComponent) { // If it is a component, go to the initInternalComponent method to merge options. If it is not, go to the else merge options
initInternalComponent(vm, options)
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
)
}
vm._self = vm
initLifecycle(vm) $parent, $root, $children, etc
initEvents(vm)// Initialize the custom event, < children@click =" XXX "> we register the event listener on the component that is not itself the parent component. Event dispatchers and listeners are both subcomponents
initRender(vm) // Parse the component's slot information to get $solt. Out of the render function to get the CreateElement method
callHook(vm, 'beforeCreate') // Call the lifecycle beforeCreate hook function
initInjections(vm) // Initialize the inject configuration item of the component, get the configuration object in the form of result[key] = val, and then process the result data in response, and proxy each key to the VM instance
initState(vm) // Initialize ROps, Methods, data, computed, and watch
initProvide(vm) // Resolve the provide object on the component configuration item and mount it to the VM. _provided property
callHook(vm, 'created') // Call the lifecycle created hook function
if (vm.$options.el) { // Execute the hung method if the parameter has el
vm.$mount(vm.$options.el)
}
}
Copy the code
$mount
src\platforms\web\entry-runtime-with-compiler.js
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
) :Component {
el = el && query(el)
/* istanbul ignore if */
if (el === document.body || el === document.documentElement) { process.env.NODE_ENV ! = ='production' && warn(
`Do not mount Vue to <html> or <body> - mount to normal elements instead.`
)
return this
}
const options = this.$options
// resolve template/el and convert to render function
if(! options.render) {let template = options.template
if (template) {
if (typeof template === 'string') {
if (template.charAt(0) = = =The '#') {
template = idToTemplate(template)
/* istanbul ignore if */
if(process.env.NODE_ENV ! = ='production' && !template) {
warn(
`Template element not found or is empty: ${options.template}`.this)}}}else if (template.nodeType) {
template = template.innerHTML
} else {
if(process.env.NODE_ENV ! = ='production') {
warn('invalid template option:' + template, this)}return this}}else if (el) {
template = getOuterHTML(el)
}
if (template) {
/* istanbul ignore if */
if(process.env.NODE_ENV ! = ='production' && config.performance && mark) {
mark('compile')}const { render, staticRenderFns } = compileToFunctions(template, {
shouldDecodeNewlines,
delimiters: options.delimiters,
comments: options.comments
}, this)
options.render = render
options.staticRenderFns = staticRenderFns
/* istanbul ignore if */
if(process.env.NODE_ENV ! = ='production' && config.performance && mark) {
mark('compile end')
measure(`vue The ${this._name} compile`.'compile'.'compile end')}}}return mount.call(this, el, hydrating)
}
/ / it is probably a piece of code to provide a $mount method, the execution method to find any render no find the template for find el take
Copy the code
This is what new vue(options) does
responsive
The important thing to notice here is that vue creates a response to initState, and initData ends up executing observe in addition to all data in the proxy. So let’s see what this method does.
src\core\observer\index.js
export function observe (value: any, asRootData: ? boolean) :Observer | void {
if(! isObject(value) || valueinstanceof VNode) {
return
}
let ob: Observer | void
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if( observerState.shouldConvert && ! isServerRendering() && (Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) && ! value._isVue ) { ob =new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
// This method probably determines whether the data passed in is an object or an array. If it is, the Observer constructor is executed to create an Observer
Copy the code
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that has this object as root $data
constructor (value: any) {
this.value = value
this.dep = new Dep() // Instantiate a Dep in each key
this.vmCount = 0
def(value, '__ob__'.this) // Bind each key to an __ob__ attribute to place an Observer instance
if (Array.isArray(value)) { // Check whether it is an array
const augment = hasProto // Determine whether __proto__ can be used
? protoAugment // If you can use the override method to reassign the prototype
: copyAugment // If not, override the prototype
augment(value, arrayMethods, arrayKeys)
this.observeArray(value) // Continue to loop through the array to see if there are any other objects or arrays in the array
} else {
this.walk(value) // In the case of an object, iterate over the keys and add a setter and getter to each key}}Copy the code
Here is a brief introduction to such a few classes, you can first have a general impression and then through the source code bit by bit in-depth.
- The publisher Dep class constructor defines the subs collection, which holds all registered subscriber instances, and the UID bit identifies the publisher object. The addSub and removeSub methods add and remove subscribers and maintain the collection of instances. The Depend method adds the current Watcher instance object to the subs collection. The notify method, which facilitates subscriber instances in subs, calls the update function. Dep deals with dom and Wathcer.
- The subscriber watcher class calls the GET method on instantiation to get the value of the current listener property, triggers the get method on that property, and calls the dep.depend method to add the subscription instance to the publisher DEp. The run method receives the change notification, compares the values before and after the data, and calls the CB to implement the view update
Something like that
Bar ID :1 subs:[watchter,watcher] foo ID :2 subs:[watcher] zoo ID :3 subs:[] Each ID has a unique ID. A subs queue that holds the Watcher instance
The above code instantiates Dep in the Observer. Let’s take a look at where watcher instantiates it. Let’s first skip the logic that parses the HTML into an AST, converts the AST into a string, and then generates the render function. The mount method is called after the render function is generated. Note that this method is not a copy of entry runtime-with-compiler.js, it is the Vue prototype method in SRC \platforms\web\runtime\index.js. The mountComponent method is called inside
src\core\instance\lifecycle.js
export function mountComponent (vm: Component, el: ? Element, hydrating? : boolean) :Component {
vm.$el = el
// If no parsed render function is retrieved, a warning is thrown
// Render is generated by parsing the template file
if(! vm.$options.render) { vm.$options.render = createEmptyVNodeif(process.env.NODE_ENV ! = ='production') {
/* istanbul ignore if */
if ((vm.$options.template && vm.$options.template.charAt(0)! = =The '#') ||
vm.$options.el || el) {
warn(
'You are using the runtime-only build of Vue where the template ' +
'compiler is not available. Either pre-compile the templates into ' +
'render functions, or use the compiler-included build.',
vm
)
} else {
// Failed to obtain the vue template file
warn(
'Failed to mount component: template or render function not defined.',
vm
)
}
}
}
callHook(vm, 'beforeMount') // Execute the beforeMount hook function
let updateComponent
/* istanbul ignore if */
if(process.env.NODE_ENV ! = ='production' && config.performance && mark) {
updateComponent = () = > {
const name = vm._name
const id = vm._uid
const startTag = `vue-perf-start:${id}`
const endTag = `vue-perf-end:${id}`
mark(startTag)
const vnode = vm._render()
mark(endTag)
measure(`vue ${name} render`, startTag, endTag)
mark(startTag)
vm._update(vnode, hydrating)
mark(endTag)
measure(`vue ${name} patch`, startTag, endTag)
}
} else {
updateComponent = () = > {
vm._update(vm._render(), hydrating)
}
}
vm._watcher = new Watcher(vm, updateComponent, noop) // Add a wathcer instance to each component (this is different from vue1 which gives a watcher to each template variable)
hydrating = false
// manually mounted instance, call mounted on self
// mounted is called for render-created child components in its inserted hook
if (vm.$vnode == null) {
vm._isMounted = true
callHook(vm, 'mounted') // Call the Mounted hook function
}
return vm
}
Copy the code
Asynchronous update
When we change the data, we trigger the setter interceptor, which executes the dep.notify() method. Let’s see, what does this method do?
src\core\observer\dep.js
notify () {
// stabilize the subscriber list first
const subs = this.subs.slice()
if(process.env.NODE_ENV ! = ='production' && !config.async) {
// subs aren't sorted in scheduler if not running async
// we need to sort them now to make sure they fire in correct
// order
subs.sort((a, b) = > a.id - b.id)
}
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
// Make a copy of the subs and sort by id, traversing the subs to execute each update() method in watcher
Copy the code
src\core\observer\watcher.js
update () {
if (this.lazy) { // Set dirty to true for lazy execution to allow computedGetter to recalcompute the value of the callback function for computed
this.dirty = true
} else if (this.sync) {
// Add sync:true when using $watcher and the watcher option
// Update directly
this.run()
} else {
// This is the longest way to put watcher in a queue
queueWatcher(this)}}Copy the code
src\core\observer\scheduler.js
const queue: Array<Watcher> = []
const activatedChildren: Array<Component> = []
let has: { [key: number]: ?true } = {}
let circular: { [key: number]: number } = {}
let waiting = false
let flushing = false
let index = 0.export function queueWatcher (watcher: Watcher) {
const id = watcher.id
// Determine whether this id exists in the global has. If it exists, it will not be queued for subsequent operations
if (has[id] == null) { Null means watcher is in the update queue but has been or is being updated,true means watcher is in the update queue and has not been updated, and undefined means watcher is not in the update queue.
has[id] = true // Mark the current ID for subsequent determination
if(! flushing) {// Put the watcher directly in the global queue if the queue is not currently refreshed
queue.push(watcher)
} else {
// If the queue has been flushed
// Traverse the queue in reverse order and insert watcher at queue position +1 corresponding to watcher.id to ensure that the queue order does not change
let i = queue.length - 1
while (i > index && queue[i].id > watcher.id) {
i--
}
queue.splice(i + 1.0, watcher)
}
// queue the flush
if(! waiting) {// A flag indicating whether the flushSchedulerQueue method is passed to nextTick
waiting = true
if(process.env.NODE_ENV ! = ='production' && !config.async) {
flushSchedulerQueue() // Call directly
return
}
nextTick(flushSchedulerQueue) FlushSchedulerQueue; // Insert the flushSchedulerQueue function into the Callbacks array}}}Copy the code
src\core\util\next-tick.js
const callbacks = []
let pending = false
// Parameter one cb one context
export function nextTick (cb? :Function, ctx? :Object) {
let _resolve
// Put CBF into callBcaks and trycatch the error easily
callbacks.push(() = > {
if (cb) {
try {
cb.call(ctx)
} catch (e) {
handleError(e, ctx, 'nextTick')}}else if (_resolve) {
_resolve(ctx)
}
})
if(! pending) {// timerFunc()
pending = true
timerFunc()
}
// $flow-disable-line
if(! cb &&typeof Promise! = ='undefined') {
return new Promise(resolve= > {
_resolve = resolve
})
}
}
Copy the code
let timerFunc
if (typeof Promise! = ='undefined' && isNative(Promise)) {
const p = Promise.resolve()
timerFunc = () = > {
p.then(flushCallbacks)
if (isIOS) setTimeout(noop)
}
isUsingMicroTask = true
} else if(! isIE &&typeofMutationObserver ! = ='undefined' && (
isNative(MutationObserver) ||
MutationObserver.toString() === '[object MutationObserverConstructor]'
)) {
let counter = 1
const observer = new MutationObserver(flushCallbacks)
const textNode = document.createTextNode(String(counter))
observer.observe(textNode, {
characterData: true
})
timerFunc = () = > {
counter = (counter + 1) % 2
textNode.data = String(counter)
}
isUsingMicroTask = true
} else if (typeofsetImmediate ! = ='undefined' && isNative(setImmediate)) {
timerFunc = () = > {
setImmediate(flushCallbacks)
}
} else {
timerFunc = () = > {
setTimeout(flushCallbacks, 0)}}// Call the flushSchedulerQueue method stored in callbacks in async. If the browser supports a native Promise or MutationObserver, this means the microtask to be used; otherwise, this means the callback to be executed in a macro task. In this case, the flushSchedulerQueue passed in the nextTick method is executed only when all synchronization code in the execution stack has been executed
function flushCallbacks () {
pending = false / / reset pendging
const copies = callbacks.slice(0) // Copy all callbacks
callbacks.length = 0
for (let i = 0; i < copies.length; i++) {
copies[i]() // Loop through execution}}Copy the code
compile
As the most difficult part of the whole VUE2, let’s take a step by step analysis.
if (vm.$options.el) {
vm.$mount(vm.$options.el)
}
// After initialization, we execute a $mount method.
Copy the code
src\platforms\web\runtime\index.js
Vue.prototype.$mount = function (
el?: string | Element,
hydrating?: boolean
) :Component {
el = el && query(el) // Mount elements
/* istanbul ignore if */
// Not the body and HTML elements, because a subsequent update will copy all the existing nodes, create the replicated nodes, and replace the original nodes
if (el === document.body || el === document.documentElement) { process.env.NODE_ENV ! = ='production' && warn(
`Do not mount Vue to <html> or <body> - mount to normal elements instead.`
)
return this
}
const options = this.$options
// resolve template/el and convert to render function
// If render is in the option, the compile phase is skipped
if(! options.render) {let template = options.template
if (template) {
/ / handle the template
if (typeof template === 'string') {
if (template.charAt(0) = = =The '#') {
template = idToTemplate(template)
/* istanbul ignore if */
if(process.env.NODE_ENV ! = ='production' && !template) {
warn(
`Template element not found or is empty: ${options.template}`.this)}}}else if (template.nodeType) {
template = template.innerHTML
} else {
if(process.env.NODE_ENV ! = ='production') {
warn('invalid template option:' + template, this)}return this}}else if (el) {
template = getOuterHTML(el)
}
if (template) {
// After processing, enter the compile phase
/* istanbul ignore if */
if(process.env.NODE_ENV ! = ='production' && config.performance && mark) {
mark('compile')}// Compile the template to get a string of functions, one dynamically compiled and one statically compiled
const { render, staticRenderFns } = compileToFunctions(template, {
outputSourceRange: process.env.NODE_ENV ! = ='production',
shouldDecodeNewlines,
shouldDecodeNewlinesForHref,
// Delimiter: {{}}
delimiters: options.delimiters,
comments: options.comments
}, this)
// Put the compiled function in $options
options.render = render
options.staticRenderFns = staticRenderFns
/* istanbul ignore if */
if(process.env.NODE_ENV ! = ='production' && config.performance && mark) {
mark('compile end')
measure(`vue The ${this._name} compile`.'compile'.'compile end')}}}// Finally mount
return mount.call(this, el, hydrating)
}
Copy the code
src\compiler\to-function.js
export function createCompileToFunctionFn (compile: Function) :Function {
const cache = Object.create(null) // Create a closure to implement a cache
return function compileToFunctions (template: string, options? : CompilerOptions, vm? : Component) :CompiledFunctionResult {
options = extend({}, options) // Merge configuration items
const warn = options.warn || baseWarn // Create an error log
delete options.warn
/* istanbul ignore if */
if(process.env.NODE_ENV ! = ='production') {
// detect possible CSP restriction
try {
new Function('return 1')}catch (e) {
if (e.toString().match(/unsafe-eval|CSP/)) {
warn(
'It seems you are using the standalone build of Vue.js in an ' +
'environment with Content Security Policy that prohibits unsafe-eval. ' +
'The template compiler cannot work in this environment. Consider ' +
'relaxing the policy to allow unsafe-eval or pre-compiling your ' +
'templates into render functions.')}}}// If there is a cache, skip the compilation and get the last result directly
const key = options.delimiters
? String(options.delimiters) + template
: template
if (cache[key]) {
return cache[key]
}
// Run the compile function to get the compilation result
const compiled = compile(template, options)
// check compilation errors/tips
if(process.env.NODE_ENV ! = ='production') {
if (compiled.errors && compiled.errors.length) {
if (options.outputSourceRange) {
compiled.errors.forEach(e= > {
warn(
`Error compiling template:\n\n${e.msg}\n\n` +
generateCodeFrame(template, e.start, e.end),
vm
)
})
} else {
warn(
`Error compiling template:\n\n${template}\n\n` +
compiled.errors.map(e= > ` -${e}`).join('\n') + '\n',
vm
)
}
}
if (compiled.tips && compiled.tips.length) {
if (options.outputSourceRange) {
compiled.tips.forEach(e= > tip(e.msg, vm))
} else {
compiled.tips.forEach(msg= > tip(msg, vm))
}
}
}
// turn code into functions
const res = {}
const fnGenErrors = []
res.render = createFunction(compiled.render, fnGenErrors) // Create dynamic function on res.render assignment
res.staticRenderFns = compiled.staticRenderFns.map(code= > {
return createFunction(code, fnGenErrors)
}) // Create a static function on an assignment to res.staticrenderfns
// check function generation errors.
// this should only happen if there is a bug in the compiler itself.
// mostly for codegen development use
/* istanbul ignore if */
if(process.env.NODE_ENV ! = ='production') {
if((! compiled.errors || ! compiled.errors.length) && fnGenErrors.length) { warn(`Failed to generate render function:\n\n` +
fnGenErrors.map(({ err, code }) = > `${err.toString()} in\n\n${code}\n`).join('\n'),
vm
)
}
}
// Save the res result
return (cache[key] = res)
}
}
Copy the code
src\compiler\create-compiler.js
export function createCompilerCreator (baseCompile: Function) :Function {
return function createCompiler (baseOptions: CompilerOptions) {
function compile (template: string, options? : CompilerOptions) :CompiledResult {
// Create a finalOptions prototype with baseOptions
const finalOptions = Object.create(baseOptions)
const errors = []
const tips = []
let warn = (msg, range, tip) = > {
(tip ? tips : errors).push(msg)
}
// Merge options into finalOptions, if any
if (options) {
if(process.env.NODE_ENV ! = ='production' && options.outputSourceRange) {
// $flow-disable-line
const leadingSpaceLength = template.match(/^\s*/) [0].length
warn = (msg, range, tip) = > {
const data: WarningMessage = { msg }
if (range) {
if(range.start ! =null) {
data.start = range.start + leadingSpaceLength
}
if(range.end ! =null) {
data.end = range.end + leadingSpaceLength
}
}
(tip ? tips : errors).push(data)
}
}
// Merge module into finalOptions
if (options.modules) {
finalOptions.modules =
(baseOptions.modules || []).concat(options.modules)
}
// Merge directives to finalOptions
if (options.directives) {
finalOptions.directives = extend(
Object.create(baseOptions.directives || null),
options.directives
)
}
// Merge the other options
for (const key in options) {
if(key ! = ='modules'&& key ! = ='directives') {
finalOptions[key] = options[key]
}
}
}
finalOptions.warn = warn
/ / the compilation of the template
const compiled = baseCompile(template.trim(), finalOptions)
if(process.env.NODE_ENV ! = ='production') {
detectErrors(compiled.ast, warn)
}
compiled.errors = errors
compiled.tips = tips
return compiled
}
return {
compile,
compileToFunctions: createCompileToFunctionFn(compile)
}
}
}
Copy the code
src\compiler\index.js
function baseCompile (template: string, options: CompilerOptions) :CompiledResult {
const ast = parse(template.trim(), options)
// Parse the template string into an AST
if(options.optimize ! = =false) {
optimize(ast, options) / / optimization of ast
}
const code = generate(ast, options) // Generate the render function
return {
ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
}
Copy the code
diff
When the component is updated, watcher executes an update method, which executes the vm._render() function to get the virtual DOM, and then executes patch.
Vue.prototype._update = function (vnode: VNode, hydrating? : boolean) {
const vm: Component = this
const prevEl = vm.$el // The elements of the page
const prevVnode = vm._vnode / / old vnode
const restoreActiveInstance = setActiveInstance(vm)
vm._vnode = vnode // Pass the new vNode
// Vue.prototype.__patch__ is injected in entry points
// based on the rendering backend used.
if(! prevVnode) {// If there is no old VNode, the page initialization is directly mounted to the new node without comparison
// initial render
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)}else {
// Update the components. Compare the old vNode with the new vnode
// updates
vm.$el = vm.__patch__(prevVnode, vnode)
}
restoreActiveInstance()
// update __vue__ reference
if (prevEl) {
prevEl.__vue__ = null
}
if (vm.$el) {
vm.$el.__vue__ = vm
}
// if parent is an HOC, update its $el as well
if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
vm.$parent.$el = vm.$el
}
// updated hook is called by the scheduler to ensure that children are
// updated in a parent's updated hook.
}
Copy the code
export const patch: Function = createPatchFunction({ nodeOps, modules })
// Patch executes the createPatchFunction method and passes methods that operate on the DOM, attr, class, style, even, directive, and ref.
Copy the code
src\core\vdom\patch.js
// This file is the core of diff
// The updateChildren method is important
// Diff makes four possibilities: delete a vnode from an old node with vnode and add a vnode from a new node without Vnode. The old node has Vnode and the new node has Vnode. The comparison starts.
// Compare the first item of the old node to the first item of the new node.
// If there is no match to compare the last item of the new node. If you have not compared to the last comparison using the old node. If you find the same node, move it to the right location.
// If the old vnode loop ends first, the remaining vnodes are added in batches. If the new Vnode cycle ends, the old Vnodes are deleted in batches
function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
// Initialize: the old node starts and ends the index, the new node starts and ends the index, and records the first vnode and last vnode of the old node and the first vnode and last vnode of the new node
let oldStartIdx = 0
let newStartIdx = 0
let oldEndIdx = oldCh.length - 1
let oldStartVnode = oldCh[0]
let oldEndVnode = oldCh[oldEndIdx]
let newEndIdx = newCh.length - 1
let newStartVnode = newCh[0]
let newEndVnode = newCh[newEndIdx]
let oldKeyToIdx, idxInOld, vnodeToMove, refElm
constcanMove = ! removeOnlyif(process.env.NODE_ENV ! = ='production') {
checkDuplicateKeys(newCh) // Check whether the key of the new node is the same
}
// Start the loop on the old and new nodes, and stop the loop if one of them completes
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (isUndef(oldStartVnode)) {
// Adjust the vNode corresponding to the index to ensure that vNodes exist
oldStartVnode = oldCh[++oldStartIdx]
} else if (isUndef(oldEndVnode)) {
oldEndVnode = oldCh[--oldEndIdx]
} else if (sameVnode(oldStartVnode, newStartVnode)) {
// Run the patchVnode command to start the new and old nodes on the same node. After the end, the indexes are +1 respectively
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
oldStartVnode = oldCh[++oldStartIdx]
newStartVnode = newCh[++newStartIdx]
} else if (sameVnode(oldEndVnode, newEndVnode)) {
// If the end node of the new and old nodes is the same node, run patchVnode. After the end node, the index is -1
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
oldEndVnode = oldCh[--oldEndIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldStartVnode, newEndVnode)) {
// The start node of the old node is the same as the end node of the new node. Run patchVnode. After the end, the index of the old node is +1 and the index of the new node is -1
patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
oldStartVnode = oldCh[++oldStartIdx]
newEndVnode = newCh[--newEndIdx]
} else if (sameVnode(oldEndVnode, newStartVnode)) {
// The end node of the old node is the same as the start node of the new node. Run patchVnode to set the index of the old node to -1 and the index of the new node to + 1
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
oldEndVnode = oldCh[--oldEndIdx]
newStartVnode = newCh[++newStartIdx]
} else {
if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
idxInOld = isDef(newStartVnode.key)
? oldKeyToIdx[newStartVnode.key]
: findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
if (isUndef(idxInOld)) { // New element
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
} else {
vnodeToMove = oldCh[idxInOld]
if (sameVnode(vnodeToMove, newStartVnode)) {
patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
oldCh[idxInOld] = undefined
canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
} else {
// same key but different element. treat as new element
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
}
}
newStartVnode = newCh[++newStartIdx]
}
}
if (oldStartIdx > oldEndIdx) {
// If the old vNode stops batch adding first
refElm = isUndef(newCh[newEndIdx + 1])?null : newCh[newEndIdx + 1].elm
addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
} else if (newStartIdx > newEndIdx) {
// If the batch deletion of new VNodes ends first
removeVnodes(oldCh, oldStartIdx, oldEndIdx)
}
}
Copy the code
conclusion
A general Vue analysis has been completed, and novice students to see the source code dizzy, do not understand are normal. The way I learned it was to break in and do it step by step and look at the results of each step, and then slowly string them together. Draw a general picture of your brain and refine it step by step. Whenever there is a problem, first analyze what Vue has done before and guess how to solve it next. There is usually a little bit of accumulation, do not think a mouthful to eat into a fat man!