At present, the community has a lot of vue. js source code analysis article, a lot of big cow write are very detailed, but in the final analysis. Light to see their own articles not to study the source code and summary notes, after all, will not be in-depth understanding and memory.

This article takes notes and records some of my own research into vue.js source code. As the saying goes, it is better to write by hand than to read a hundred times.

Understand what the MVVM pattern is?

The MVC pattern is that the user operation will request the server route, and the route will call the corresponding controller for processing, and the controller will get the data. The result is returned to the front end and the page is re-rendered. And the front-end will manually manipulate the DOM rendering of the data to the page, which is very performance consuming.

While not entirely following the MVVM model, the design of Vue was inspired by it. In Vue, instead of manually manipulating DOM elements, the data is bound to the viewModel layer. The data is automatically rendered to the page, and the viewModel layer is notified to update the data when the view changes. The ViewModel is the bridge in our MVVM pattern.


What is the rationale for reactive data in Vue (version 2.x)?

The principle behind the ve2. X version of reactive data is Object.defineProperty(detailed in the Es6 notes)

When Vue initializes, that is, when new Vue(), it calls an underlying initData() method. An Observe () in the observe() method controls the initialization of the incoming data in a responsive manner, and performs a series of operations on the data to determine whether it has been observed. Determine whether the observed data is an object or an array.

The observed data is the object

If an Object is being observed, a walk() method is called that calls Object.defineProperty to observe, and if the attribute inside the Object is still an Object, the recursive observation is done.

In this case, when the current object is evaluated, the get method will be called, and the dependency collection (watcher) will be carried out in the GET method. If the current object is assigned, the set method will be called, and the set method will determine whether the old and new values are different. Otherwise, a notify method is called to trigger the dependency collection for data updates.

The observed data is an array

If the observation is an array, the array will not rely on the above method for collection. Vue overwrites the prototype method of the array. When the current observation is an array, Vue points the prototype of the array to its own defined prototype method. And only the methods of the following seven arrays are intercepted.

// Because only the following 7 array methods can change the original array. push, pop,shift, unshift, splice, sort, reverse
Copy the code

The prototype method uses function hijacking internally. If the user operates on the array methods in the above 7, the array method will be rewritten by Vue. When the array changes, you can manually call notify to update the attempt.

Of course, when the data is updated, the newly added data will also be collected and observed.

If the data in the array is also an Object, it continues to observe it by calling Object.defineProperty.

With that in mind, you can understand why the array modifies the data by subscript, and the data changes but the view is not updated.

Observation object core code

Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      if(dep.target) {dep.depend() // ** Collect dependencies ** /if (childOb) {
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      const value = getter ? getter.call(obj) : val
      if(newVal === value || (newVal ! == newVal && value ! == value)) {return
      }
      if(process.env.NODE_ENV ! = ='production'&& customSetter) { customSetter() } val = newVal childOb = ! Shallow && observe(newVal) dep.notify() /** Notify dependencies **/}})Copy the code

Observe the array core code

const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
const methodsToPatch = [
  'push'.'pop'.'shift'.'unshift'.'splice'.'sort'.'reverse'
]
methodsToPatch.forEach(functionConst original = arrayProto[method] def(arrayMethods, method,functionmutator (... args) { const result = original.apply(this, args) const ob = this.__ob__let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    if(inserted) ob.observearray (inserted) // notify change ob.dep.notify() // Manually notify the view when the array method is calledreturnResult})}) this.observearray (valueCopy the code

The above content is best to download Github Vue source code together.


What is the reason for Vue’s asynchronous rendering?

The first thing to know is that Vue is a component-level update.

When we operate on methods in a component to update data, for example

data() {
    return {
        msg: 'hello word',
        name: 'Just tomato and eggs.'
    }
}
methods:{
    add() {
        this.msg = 'I have changed => hello word'
        this.name = 'I changed => only tomato scrambled eggs'}}Copy the code

Rendering the view as soon as the data is changed (twice above) inevitably affects performance, so Vue uses asynchronous rendering, where multiple data are simultaneously changed in a single event, and the same Watcher is fired multiple times and only pushed to the queue once. The Nexttick method is called to asynchronously update the view when the last data has been changed.

There are other internal operations, such as adding watcher with a unique ID, updating with a sort based on the ID, and calling the corresponding lifecycle (beforeUpdate and updated methods) after updating.

The above content is best to download Github Vue source code together.


How does nextTick work in Vue?

Before you know how nextTick works, you need to know what Event loops are, and you need to know about microtasks and macros, which I’ll briefly describe here.

Event Loop

We also know that when we execute JS code is actually put functions on the execution stack, so when we encounter asynchronous code how to do? In fact, when asynchronous code is encountered, it is suspended and added to the Task queue (there are multiple tasks) when it needs to be executed. Once the execution stack is empty, the Event Loop takes the code that needs to be executed from the Task queue and puts it into the execution stack for execution, so it is essentially asynchronous or synchronous behavior in JS.

Different Task sources are assigned to different Task queues. Task sources can be divided into microtasks and macrotasks. In the ES6 specification, microtasks are called Jobs and MacroTasks are called tasks.

Microtasks include process.nextTick, Promise. then, and MutationObserver, where process.nextTick is unique to Node.

Macro tasks include Script, setTimeout, setInterval, setImmediate, I/O, and UI Rendering.

After a brief understanding of Event Loop, continue to learn the implementation principle of nextTick in Vue

Vue internally attempts to use native Promise.then, MutationObserver, and setImmediate for asynchronous queues, and setTimeout(fn, 0) instead if the execution environment does not support it.

The official as saying

When you set vm.someData = ‘new Value ‘, the component does not immediately re-render. When the queue is refreshed, the component is updated in the next event loop “TICK”. In most cases we don’t need to worry about this process, but if you want to do something based on the updated DOM state, it can be tricky. While vue.js generally encourages developers to think in a “data-driven” way and avoid direct contact with the DOM, sometimes we have to. To wait for Vue to finish updating the DOM after the data changes, use vue.nexttick (callback) immediately after the data changes. This callback will be called after the DOM update is complete.

Summary: The nextTick method mainly uses macro tasks and micro tasks, and defines an asynchronous method. Multiple calls to nextTick will queue the method, and this asynchronous method will clear the current queue. So this nextTick method is an asynchronous method

NextTick principle core code

letTimerFunc // defines an asynchronous methodif(typeof Promise ! = ='undefined' && isNative(Promise)) {  // promise
  const p = Promise.resolve()
  timerFunc = () => {
    p.then(flushCallbacks)
    if (isIOS) setTimeout(noop)
  }
  isUsingMicroTask = true
} else if(! isIE && typeof MutationObserver ! = ='undefined' && ( // MutationObserver
  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 (typeof setImmediate ! = ='undefined'{/ /setImmediate
  timerFunc = () => {
    setImmediate(flushCallbacks)
  }
} else {
  timerFunc = () => {   // setTimeout
    setTimeout(flushCallbacks, 0)}} // nextTick implementationexport functionnextTick (cb? : Function, ctx? : Object) {let _resolve
  callbacks.push(() => {
    if (cb) {
      try {
        cb.call(ctx)
      } catch (e) {
        handleError(e, ctx, 'nextTick')}}else if (_resolve) {
      _resolve(ctx)
    }
  })
  if(! pending) { pending =true
    timerFunc()
  }
}
Copy the code

The above content is best to download Github Vue source code together.


Principle of computed in Vue

Many interview questions ask the difference between computed and Watch. Actually, both computed and Watch use Watcher, and the difference between computed and Watch is that it has the function of caching.

Computed cache capability

When we initialize the calculated properties by default, it creates a watcher with two properties lazy:true, dirty: True, which means that when a calculated property is created, it is not executed by default, only when the user values it (that is, when it is used on the component). It will tell the Watcher to evaluate if dirty: true, and when it is done, it will change the dirty: False, so that when you use the evaluation again and the condition goes dirty: false, you don’t do the Watcher evaluation, but return the result of the last evaluation.

So when do you recalculate your job search?

It only calls the corresponding UPDATE method if the value of the evaluated property changes, then changes dirty: true, and then re-performs the Watcher evaluation based on the condition at execution time.

Computed principle core code

function initComputed (vm: Component, computed: Object) {
  const watchers = vm._computedWatchers = Object.create(null)
  const isSSR = isServerRendering()
  for (const key in computed) {
    const userDef = computed[key]
    const getter = typeof userDef === 'function' ? userDef : userDef.get
    if(! isSSR) { // create internal watcherfor the computed property.
      watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
      )
    }

    // component-defined computed properties are already defined on the
    // component prototype. We only need to define computed properties defined
    // at instantiation here.
    if(! (keyin vm)) {
      defineComputed(vm, key, userDef)
    } else if(process.env.NODE_ENV ! = ='production') {
      if (key in vm.$data) {
        warn(`The computed property "${key}" is already defined in data.`, vm)
      } else if (vm.$options.props && key in vm.$options.props) {
        warn(`The computed property "${key}" is already defined as a prop.`, vm)
      }
    }
  }
}
function createComputedGetter (key) {
  return function computedGetter () {
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      if(watcher.dirty) {// Evaluate () {evaluate()}if (Dep.target) {
        watcher.depend()
      }
      return watcher.value
    }
  }
}
Copy the code

The above content is best to download Github Vue source code together.


How is deep: True implemented in Watch?

Vue official introduction about Watch

  • Type: {[key: string] : string | Function | Object | Array}

  • Detail: an object whose key is the expression to observe and whose value is the corresponding callback function. The value can also be a method name, or an object that contains options. The Vue instance will call $watch() at instantiation time, iterating through each property of the Watch object.

In general, we use Watch in projects to monitor the changes of routing or data attributes and make corresponding processing methods.

So deep: True is used when the property being monitored is an Object, and the method being monitored in Watch is not executed. Due to the limitations of modern JavaScript (and deprecation of Object.Observe), Vue cannot detect the addition or deletion of Object properties. Since Vue performs getter/setter conversion procedures on the property when it initializes the instance, the property must exist on the Data object for Vue to convert it so that it is responsive.

Deep means to look deep, and the listener will go down one layer at a time, adding the listener to all the properties of the object, but the performance overhead is very high. Any modification of any property in OBJ will trigger the handler in the listener.

At this point we can optimize the problem in the following way

// Listen for a value in a concrete object as a string. watch: {'obj.a': {
    handler(newName, oldName) {
      console.log('obj.a changed');
    },
    immediate: true// Immediately execute the handler method deep:true// Depth monitoring}}Copy the code

It should be noted that when we modify a value in the array by subscript, watch will not change. Please see the principle of responsive data in Vue above.

Of course, in addition to the array change method to monitor array changes, Vue also provides the vue.set () method.

Deep: True core code in Watch

get() {pushTarget(this) // First place the current dependency on dep.targetlet value
    const vm = this.vm
    try {
      value = this.getter.call(vm, vm)
    } catch (e) {
      if (this.user) {
        handleError(e, vm, `getter for watcher "${this.expression}"`)}else {
        throw e
      }
    } finally {
      if(this.deep) {// traverse(value) traverse(value) {popTarget()}return value
}
function _traverse (val: any, seen: SimpleSet) {
  let i, keys
  const isA = Array.isArray(val)
  if((! isA && ! isObject(val)) || Object.isFrozen(val) || val instanceof VNode) {return
  }
  if (val.__ob__) {
    const depId = val.__ob__.dep.id
    if (seen.has(depId)) {
      return
    }
    seen.add(depId)
  }
  if (isA) {
    i = val.length
    while (i--) _traverse(val[i], seen)
  } else {
    keys = Object.keys(val)
    i = keys.length
    while (i--) _traverse(val[keys[i]], seen)
  }
}
Copy the code

The above content is best to download Github Vue source code together.


Lifecycle in Vue

Attached is the Vue official introduction chart about the life cycle

When is each lifecycle function called?

  • beforeCreate(){}
Data observe is invoked after instance initialization and before data Observe. The data in data cannot be retrieved.Copy the code
  • created(){}
Called after instance creation, when the instance has completed data Observe, property and method operations, and watch/ Event event callbacks. Note: Not here$el
Copy the code
  • beforeMount(){}
Called before the mount, the associated render function is called for the first time.Copy the code
  • mounted(){}
The el bound element is newly created internally$elCalled after being replaced and mounted to the instance.Copy the code
  • beforeUpdate(){}
Called when data is updated, before the virtual DOM is re-rendered and patched.Copy the code
  • updated(){}
This hook is called after the virtual DOM is re-rendered and patched due to data changes.Copy the code
  • beforeDestroy(){}
Called before instance destruction. At this step, the instance is still fully availableCopy the code
  • destroyed(){}
Called after the Vue instance is destroyed. After the invocation, everything indicated by the Vue instance is unbound and all event listeners are removed. All subinstances are also destroyed, and the hook is not called during server-side rendering.Copy the code

Common things that can be done within the lifecycle.

  • created(){}
Usually we will be in the projectcreated(){} life cycle to call Ajax for some data resource requests, but because the current life cycle cannot manipulate DOM, generally in the project, I will put all the requestsmounted(){} in the life cycle.Copy the code
  • mounted(){}
In the current lifecycle, the instance has been mounted, and I would normally put ajax requests into this lifecycle function. If there are some DOM operations that need to be initialized based on the retrieved data, this is the best solution.Copy the code
  • beforeUpdate(){}
Further state changes can be made in this lifecycle function without triggering additional re-renderingCopy the code
  • updated(){}
You can perform DOM-dependent operations. In most cases, however, you should avoid changing the state during this period, as this can lead to an infinite update loop. This hook is not called during server-side rendering.Copy the code
  • destroyed(){}
You can perform optimization actions, empty timers, and unbind eventsCopy the code

From the above description, we can draw the following conclusions

  1. Ajax requests are typically placed in the Created (){} or Mounted (){} lifecycle. In Mounted, the DOM is already rendered, so you can directly manipulate the DOM node. In Mounted, the DOM node is already rendered, so you can directly manipulate the DOM node. In most cases, it is placed in Mounted to ensure logical uniformity. Because the lifecycle is executed synchronously, ajax is executed asynchronously.

    Note: Server rendering does not support The Mounted method, so it is added to the Created method in the case of server rendering

  2. If there is a timer in the current component, use the $on method, bind scroll mousemove and other events, need to be cleared in the beforeDestroy hook.


How templates are compiled in Vue

After looking at the source code, Vue will call a parseHTML method at the bottom to convert the template into an AST syntax tree (internally through the regular to go through some methods), and finally convert the AST syntax tree into a render function (render function), which generates a Virtual DOM tree with data. The new UI is generated after Diff and Patch.

About the Virtual DOM

After compiling the templates, Vue’s compiler compiles them into a rendering function. When called, the function renders and returns a tree of the virtual DOM. Once we have the virtual tree, we hand over the Patch function to actually apply the virtual DOM to the real DOM.

In this process, Vue has its own responsive system to detect the data sources it relies on during rendering. When the source of the data is detected during rendering, changes in the data source can be accurately sensed. You can then re-render as needed. When the rendering is done again, a new tree is generated and compared to the old tree, and the changes that should be applied to the real DOM can be determined.

Finally, the Patch function is used to make changes. In a nutshell, the underlying implementation of Vue compiles templates into virtual DOM rendering functions. Combined with Vue’s built-in response system, Vue can intelligently calculate the minimum cost of re-rendering components when the state should change and apply it to DOM operations.

In fact, this part of the source code or more. So I’m making a little bit of sense here.


About the difference between V-IF and V-show and how it’s implemented underneath.

Here’s a brief description of the difference

  • v-if
If the current condition is not true, then the DOM element of the node where the current instruction resides is not renderedCopy the code
  • v-show
The current instruction, the node of the DOM element will always be rendering is based on the current condition to the dynamic change display: none | | block so as to achieve the DOM elements show and hide.Copy the code

Underlying implementation

  • v-if

The Vue layer encapsulates some special methods, and the code is here. vue/packages/weex-vue-framework/factory.js

VueTemplateCompiler.compile(`<div v-if="true"><span v-for="i in 3">hello</span></div>`);

with(this) {
    return (true)? _c('div', _l((3), function (i) {
        return _c('span', [_v("hello")])}), 0) : the _e() // _e() method creates an empty virtual DOM and so on. }Copy the code

From the above code, you can see that if the current condition judgment is not true, then the DOM element of the node where the current instruction resides will not be rendered

  • v-show

V-show compiles there is nothing in it but a directives which has a directive called V-show

VueTemplateCompiler.compile(`<div v-show="true"></div>`);
/**
with(this) {
    return _c('div', {
        directives: [{
            name: "show",
            rawName: "v-show",
            value: (true),
            expression: "true"})}}]Copy the code

It handles this instruction only when it is run, as follows:

/ / v - is the style definitions show operation in platforms/web/runtime/directives/show. Jsbind (el: any, { value }: VNodeDirective, vnode: VNodeWithData) {
    vnode = locateNode(vnode)
    const transition = vnode.data && vnode.data.transition
    const originalDisplay = el.__vOriginalDisplay =
      el.style.display === 'none' ? ' ' : el.style.display
    if (value && transition) {
      vnode.data.show = true
      enter(vnode, () => {
        el.style.display = originalDisplay
      })
    } else {
      el.style.display = value ? originalDisplay : 'none'}}Copy the code

It is clear from the source code that it is operating on the display property of the DOM.


Why can’t V-for be used with V-IF in a project

You can also know why by looking at the source code

VueTemplateCompiler.compile(`<div v-if="false" v-for="i in 3">hello</div>`);

with(this) {
    return _l((3), function (i) {
        return (false)? _c('div', [_v("hello")]) : _e()
    })
}
Copy the code

We know that v-for has a higher priority than V-if, so at compile time we will find that it adds V-if to every internal element, which will be verified at run time, which is very costly. So we want to avoid this in our projects.

Of course, if we have such a need, it can be achieved.

We can do this by calculating properties

<div v-for="i in computedNumber">hello</div>

export default {
    data() {
        return {
            arr: [1, 2, 3]
        }
    },
    computed: {
        computedNumber() {
            return arr.filter(item => item > 1)
        }
    }
}
Copy the code

On the source code of the parse instruction, I suggest you also go to see the implementation process of the source code


About the implementation process of the virtual DOM

As I understand it, we use an object to describe our virtual DOM structure, for example:

<div id="container">< p></p> </div> // a simple object to describe the virtual DOM structurelet obj = {
    tag: 'div',
    data: {
        id: "container"
    },
    children: [
        {
            tag: 'p',
            data: {},
            children: {}
        }
    ]
}
Copy the code

Of course, the implementation in Vue is more complicated, so I’ve added some glances to make it easier to understand

const SIMPLE_NORMALIZE = 1
const ALWAYS_NORMALIZE = 2

functionCreateElement (context, tag, data, children, normalizationType, alwaysNormalize)if(Array.isArray(data) || isPrimitive(data)) { normalizationType = children children = data data = undefined } // If alwaysNormalize istrue// normalizationType should be set to the constant value of ALWAYS_NORMALIZEifNormalizationType = ALWAYS_NORMALIZE // Call _createElement to create a virtual nodereturn _createElement(context, tag, data, children, normalizationType)
    }

    function_createElement (context, tag, data, children, normalizationType) {/** * if data.__ob__, * Data cannot be used as a virtual node * a warning is thrown and an empty node is returned * * Monitored data cannot be used as vNode rendered data for the following reasons: * Data may be changed during vNode rendering, which triggers monitoring and results in undesired operations */if(data && data.__ob__) { process.env.NODE_ENV ! = ='production' && warn(
            `Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` +
            'Always create fresh vnode data objects in each render! ',
            context
            )
            returnCreateEmptyVNode ()} // When the component's is property is set to a falsy value // Vue will not know what to render the component into // so render an empty nodeif(! tag) {returnCreateEmptyVNode ()} // Scope slotif (Array.isArray(children) && typeof children[0] === 'function') { data = data || {} data.scopedSlots = { default: Children [0]} children.length = 0} // Select different processing methods according to the value of normalizationTypeif (normalizationType === ALWAYS_NORMALIZE) {
            children = normalizeChildren(children)
        } else if (normalizationType === SIMPLE_NORMALIZE) {
            children = simpleNormalizeChildren(children)
        }
        letVnode, ns // If the label name is a stringif (typeof tag === 'string') {
            letGetTagNamespace (tag) // Check whether the tag is reservedif(config. IsReservedTag (tag)) {/ / if they are retained label, creates such a vnode vnode = new vnode (config. ParsePlatformTagName (tag), data, Children, undefined, undefined, context);}else if ((Ctor = resolveAsset(context.$options.'components'Vnode = createComponent(Ctor, data, context, children, tag)) {vNode = createComponent(Ctor, data, context, children)}elseVnode = new vnode (tag, data, children, undefined, undefined, context)} Tag is a component's constructor class.else{vNode = createComponent(tag, data, context, children)} // If there is a vNodeif(vnode) {// If there is a namespace, apply the namespace and return vnodeif (ns) applyNS(vnode, ns)
            returnVnode // Otherwise, return an empty node}else {
            return createEmptyVNode()
        }
    }
}
Copy the code

The above content is best to download Github Vue source code together.


The time complexity of diFF algorithm, and the principle of DIFF algorithm in Vue

I don’t know much about algorithms, so I just look at videos and articles to describe them.

The full diff algorithm for two trees is an O(n3) time complexity, and Vue is optimized to convert ·O(n3) complexity problems into O(n) complexity problems (only comparing hierarchies and not considering cross-level problems). In the front end, you rarely move Dom elements across levels. So the Virtual Dom only compares elements at the same level.

Principle of DIFF algorithm in Vue (B station public class video and Vue source code)

  • 1. Compare peer nodes first and then child nodes
  • 2. Determine if one parent has a son and the other doesn’t
  • 3. They both have sons
  • 4. Compare child nodes recursively

Self understanding

The first case: peer comparison When the new node is different from the old node, the new node directly replaces the old node. The second case: peer comparison, the nodes are the same, but one party has child nodes, one party does not. When the old node and the new node are the same, if the new node has child nodes, but the old node does not, the old node will directly insert the child nodes of the new node into the old node. If the old node and the new node are the same, if the new node has no children but the old node has children, the old node will delete the children directly. Third case: The old and new nodes are the same and both have child nodes. (The above two-pointer comparison method is always used at this time.) Case 1: old: 1234 new: 12345 the current double pointer points to the old and new 1,1 and 4, 5. Judge that the first node is consistent, move the pointer backward and continue to judge until the last item is different, insert the new 5 after the old 4. Case 2: old: 1234 new: 01234 the current double pointer points to the old and new 1,0 and 4. If 4 does not want to wait, it will check the last pointer and move the pointer from behind to judge if it finds the same. Until you get to the head, insert the new 0 before the old 1. Situation three: old: 1234 new: 4123 Currently found that the head and head do not want to wait, and the tail and tail do not want to wait, then blend into the end of the line/end of the head of the comparison. When it finds that the old 4 is first in the new one, it will adjust its 4 before 1. Situation four: old: 1234 new: 2341 Currently found that the head and head do not want to wait, and the tail and tail do not want to wait, then blend into the end of the line/tail head of the comparison. When the old 1 is in the fourth place of the new one, it adjusts its 1 after the 4. Special case 5: (that is why we need to add the key value when we loop through the array) Old: 1234 New: 2456 At this time, recursive traversal will take the key of the new element to compare the old one and move the position. If the old one does not exist, the new one will be put in directly. Otherwise, the old one will be deleted.Copy the code

So that gives us an overview of the diff algorithm.

The core source

core/vdom/patch.js

Const oldCh = oldvnode. children const ch = vnode.children // New childif (isUndef(vnode.text)) {
    if(isDef(oldCh) &&isdef (ch)) {// Compare childrenif(oldCh ! == ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly) }else if(isDef(ch)) {// The new son has no old sonif (isDef(oldVnode.text)) nodeOps.setTextContent(elm, ' ')
        addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue)
    } else if(isDef(oldCh)) {// Remove removeVnodes(oldCh, 0, oldch.length-1)}else if(isDef(oldvnode.text)) {// old has text new has no text nodeops.settextContent (elm,' ') // Empty the old}}else if (oldVnode.text !== vnode.text) { // 文本不相同替换
    nodeOps.setTextContent(elm, vnode.text)
}
Copy the code
function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
    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

    // removeOnly is a special flag used only by <transition-group>
    // to ensure removed elements stay incorrect relative positions // during leaving transitions const canMove = ! removeOnlyif(process.env.NODE_ENV ! = ='production') {
      checkDuplicateKeys(newCh)
    }

    while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
      if (isUndef(oldStartVnode)) {
        oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
      } else if (isUndef(oldEndVnode)) {
        oldEndVnode = oldCh[--oldEndIdx]
      } else if (sameVnode(oldStartVnode, newStartVnode)) {
        patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
        oldStartVnode = oldCh[++oldStartIdx]
        newStartVnode = newCh[++newStartIdx]
      } else if (sameVnode(oldEndVnode, newEndVnode)) {
        patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
        oldEndVnode = oldCh[--oldEndIdx]
        newEndVnode = newCh[--newEndIdx]
      } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
        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)) { // Vnode moved left
        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) {
      refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
      addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue)
    } else if (newStartIdx > newEndIdx) {
      removeVnodes(oldCh, oldStartIdx, oldEndIdx)
    }
  }
Copy the code

Why is key used in V-for

For example, when we loop out three chenckboxes using for, when we delete the first item of the current loop array with a button, we will find that the first item is still selected and the last item is deleted, because of the diff process. When comparing the old and new virtual DOM, it is found that the DOM is the same. At this time, the first DOM to be deleted is reused internally (the content will be the real content, not the deleted content). After comparison, the last item of the old DOM is deleted.

1 (1 is selected) 1 (1 is selected) 2 2 3 3 (deleted)Copy the code

This might be a little confusing, but you can try it out on your own. (ps: v-for loop must add key and key cannot index)


Continuing to summarize…