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
  • 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!