preface

Last time we looked at the vUE global API, for a better comparison, let’s take a look at the instance methods in VUE.

Deep source

Index.js — entry file

As you can easily see from the previous articles, the file entry for the vue instance method is SRC \core\instance\index.js

import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'
import { warn } from '.. /util/index'

function Vue (options) {
  // Can be ignored
  if(process.env.NODE_ENV ! = ='production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')}// Initializing method: vue.prototype._init
  this._init(options)
}

// Define the this._init initialization method
initMixin(Vue)

// define the $data $props $et $dlete $watch instance method
stateMixin(Vue)

$on $once $off $emit instance method
eventsMixin(Vue)

// Define the _update $forceUpdate $destroy instance method
lifecycleMixin(Vue)

// Define the $nextTick _render instance method
renderMixin(Vue)

export default Vue
Copy the code

initMixin(Vue)

File Location:src\core\instance\init.js

This section is already covered in the initialization section, so it won’t be repeated here. Click initMixin(Vue) to see the details.

stateMixin(Vue)

File Location:src\core\instance\state.js

  • To deal withpropsdataData, defined for themgetset
    • getMethod returns the correspondingthis._prorpsthis._data
    • setMethods to controlpropsdataCannot be directly assigned to another new value
  • willdatapropsSeparately represented toVue.prototypeOn the$data$props, supported bythis.$datathis.$propsForm direct access
  • Define the instancethis.$watchmethods
export function stateMixin (Vue: Class<Component>) {
  // flow somehow has problems with directly declared definition object
  // when using Object.defineProperty, so we have to procedurally build up
  // the object here.

  // Process data, define get methods, access this._data
  const dataDef = {}
  dataDef.get = function () { return this._data }

  // Handle the props data, define the get method, and call this._props
  const propsDef = {}
  propsDef.get = function () { return this._props }

  // It is not allowed to replace the data and props attributes directly
  if(process.env.NODE_ENV ! = ='production') {
    // This.$data = newVal is not allowed
    // This.$data.key = newVal
    dataDef.set = function () {
      warn(
        'Avoid replacing instance root $data. ' +
        'Use nested data properties instead.'.this)}// Directly prompt props is read-only
    propsDef.set = function () {
      warn(`$props is readonly.`.this)}}$props = = this.$data = = this.$props = = this
  Object.defineProperty(Vue.prototype, '$data', dataDef)
  Object.defineProperty(Vue.prototype, '$props', propsDef)

  $set and this.$delete methods, which are aliases for the global vue. set and vue. delete methods
  Vue.prototype.$set = set
  Vue.prototype.$delete = del

  // Define the instance this.$watch method
  Vue.prototype.$watch = function (
    expOrFn: string | Function, cb: any, options? :Object
  ) :Function {
    const vm: Component = this
    // If cb is an object, we can ensure that the cb received is a function
    if (isPlainObject(cb)) {
      // Handle with the createWatcher method
      return createWatcher(vm, expOrFn, cb, options)
    }

    options = options || {}
    // The tag is a user watcher
    options.user = true
    // Instantiate a watcher
    const watcher = new Watcher(vm, expOrFn, cb, options)
    // Immediate executes immediately if it is true
    if (options.immediate) {
      const info = `callback for immediate watcher "${watcher.expression}"`
      pushTarget()
      / / by the apply | call call CD
      invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
      popTarget()
    }
    return function unwatchFn () {
      watcher.teardown()
    }
  }
}
Copy the code

eventsMixin(Vue)

File Location:src\core\instance\events.js

export function eventsMixin (Vue: Class<Component>) {
  const hookRE = /^hook:/
  // Listen for one or more events and place the corresponding callbacks to the vm._events object
  _events = {eventType1:[cb1,...]  , eventType1:[cb1,...] }
  Vue.prototype.$on = function (event: string | Array<string>, fn: Function) :Component {
    const vm: Component = this

    $on(event[I], fn); $on(event[I], fn)
    // this.$on(['event1','event2',...] , function(){})
    if (Array.isArray(event)) {
      for (let i = 0, l = event.length; i < l; i++) {
        vm.$on(event[i], fn)
      }
    } else {
      Vm. _events holds the events listened on the current instance. Each event type is handled in an array
      (vm._events[event] || (vm._events[event] = [])).push(fn)
      // optimize hook:event cost by using a boolean flag marked at registration
      // instead of a hash lookup
      // Set vm._hasHookEvent to true when < com@hoook :mounted="handleHoookMounted" /> is used
      if (hookRE.test(event)) {
        vm._hasHookEvent = true}}return vm
  }

  // Listen for a custom event, but only once, once triggered, the listener is removed
  Vue.prototype.$once = function (event: string, fn: Function) :Component {
    const vm: Component = this
    // Wrap the external event callback in the ON method
    // Remove the callback of the specified event before calling the method, and then call externally passed fn via apply
    function on () {
      vm.$off(event, on)
      fn.apply(vm, arguments)
    }
    on.fn = fn
    // Use the wrapped on function as an event callback in vm.$on
    vm.$on(event, on)
    return vm
  }

  /** ** Remove custom event listeners on vm._events: * 1. If no arguments are provided, remove all event listeners, vm._events = object.create (null) * 2. If only events are provided, remove all listeners for the event, vm._events[event] = null * 3. If both events and callbacks are provided, only the listener for the callback is removed, vm._events[event].splice(I,1) */
  Vue.prototype.$off = function (event? : string |Array<string>, fn? :Function) :Component {
    const vm: Component = this
    // No event is passed: removes all listening events on the current instance
    if (!arguments.length) {
      // Assign vm._events directly to a pure object
      vm._events = Object.create(null)
      return vm
    }
    // The event argument is an array: the traversal number in turn removes listening events from the instance
    if (Array.isArray(event)) {
      for (let i = 0, l = event.length; i < l; i++) {
        vm.$off(event[i], fn)
      }
      return vm
    }
    // The event argument is a string: the corresponding event callback array is retrieved from vm._events
    const cbs = vm._events[event]
    // The callback array does not exist
    if(! cbs) {return vm
    }

    // If fn is not passed, all current event types are cleared
    if(! fn) { vm._events[event] =null
      return vm
    }

    // If fn is present, loop through the event callback array to find the corresponding callback, and then delete it by splice method
    let cb
    let i = cbs.length
    while (i--) {
      cb = cbs[i]
      if (cb === fn || cb.fn === fn) {
        cbs.splice(i, 1)
        break}}return vm
  }

  Vue.prototype.$emit = function (event: string) :Component {
    const vm: Component = this
    /* HTML attributes are not case sensitive. Do not use camel names for HTML attributes, as they will all be lowercase when compiled. < comp@customEvent ="handler" /> equivalent to < comp@customEvent ="handler" /> in js: $emit('customEvent') this.$emit('customEvent') this.$emit('customEvent') this.$emit('customEvent') this.$emit('customEvent') this. < comp@custom -event="handler" /> jS: this.$emit('custom-event') */ 
    if(process.env.NODE_ENV ! = ='production') {
      const lowerCaseEvent = event.toLowerCase()
      if(lowerCaseEvent ! == event && vm._events[lowerCaseEvent]) { tip(`Event "${lowerCaseEvent}" is emitted in component ` +
          `${formatComponentName(vm)} but the handler is registered for "${event}". ` +
          `Note that HTML attributes are case-insensitive and you cannot use ` +
          `v-on to listen to camelCase events when using in-DOM templates. ` +
          `You should probably use "${hyphenate(event)}" instead of "${event}". `)}}// Get the event callback array for the corresponding event type
    let cbs = vm._events[event]
    if (cbs) {
      // Convert the class array to a real array
      cbs = cbs.length > 1 ? toArray(cbs) : cbs
      // Process the list of class array arguments and convert them to arrays
      const args = toArray(arguments.1)
      const info = `event handler for "${event}"`

      for (let i = 0, l = cbs.length; i < l; i++) {
        // Call the callback function and pass the argument to it, using a try catch to catch the exception
        invokeWithErrorHandling(cbs[i], vm, args, vm, info)
      }
    }
    return vm
  }
}
Copy the code

lifecycleMixin(Vue)

File Location:src\core\instance\lifecycle.js

Among them, vm.__patch__ in vue.prototype. _update method involves patch operation and diff algorithm, which will be interpreted separately later and skipped here for the time being.

export function lifecycleMixin (Vue: Class<Component>) {
  // The component initializes the render and update entry
  Vue.prototype._update = function (vnode: VNode, hydrating? : boolean) {
    const vm: Component = this
    const prevEl = vm.$el
    const prevVnode = vm._vnode
    const restoreActiveInstance = setActiveInstance(vm)
    vm._vnode = vnode
    // Vue.prototype.__patch__ is injected in entry points
    // based on the rendering backend used.

    // prevVnode does not exist, indicating initial render
    if(! prevVnode) {// Patch phase: Patch and DIff algorithm
      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)}else {
       // prevVnode exists, indicating a subsequent update phase
      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.
  }

  // Forces the Vue instance to be re-rendered, noting that it affects only the instance itself and the children that insert slot content, not all children
  Vue.prototype.$forceUpdate = function () {
    const vm: Component = this
    if (vm._watcher) {
      // The core is to execute the _watcher.update() method on the component instance
      vm._watcher.update()
    }
  }

  /* Completely destroy an instance: 1. Clear its connections to other instances, unbind all instructions and event listeners 2. Hooks that trigger beforeDestroy and Destroyed generally do not call this function manually, and v-if and V-for are officially recommended to control the life cycle of child components */ 
  Vue.prototype.$destroy = function () {
    const vm: Component = this
    // If the current component has already been destroyed, return directly
    if (vm._isBeingDestroyed) {
      return
    }

    // Invoke the beforeDestroy lifecycle hook
    callHook(vm, 'beforeDestroy')

    // Marks the current component for destruction
    vm._isBeingDestroyed = true
    // remove self from parent
    const parent = vm.$parent
    if(parent && ! parent._isBeingDestroyed && ! vm.$options.abstract) {// Remove the current component from the children property of the parent component
      remove(parent.$children, vm)
    }

    // teardown watchers
    // Remove all watcher associated with the current component
    if (vm._watcher) {
      vm._watcher.teardown()
    }
    let i = vm._watchers.length
    while (i--) {
      vm._watchers[i].teardown()
    }

    // remove reference from data ob
    // frozen object may not have observer.
    // Remove references to _data.__ob__ from the current component
    if (vm._data.__ob__) {
      vm._data.__ob__.vmCount--
    }

    // Marks that the current component has been destroyed
    vm._isDestroyed = true
    
    // Call the destroy hook in the current render tree
    vm.__patch__(vm._vnode, null)

    // Call destroyed to declare the cycle hooks
    callHook(vm, 'destroyed')

    // Remove all listeners for the current component
    vm.$off()

    // remove __vue__ reference
    if (vm.$el) {
      vm.$el.__vue__ = null
    }

    // release circular reference (#6759)
    // Disconnect the parent component from the current component by setting the parent component to NULL
    if (vm.$vnode) {
      vm.$vnode.parent = null}}}Copy the code

renderMixin(Vue)

File Location:src\core\instance\render.js

export function renderMixin (Vue: Class<Component>) {
  // install runtime convenience helpers
  // Mount some run-time tool methods on the component instance
  installRenderHelpers(Vue.prototype)

  // This is an alias for vue. nextTick
  Vue.prototype.$nextTick = function (fn: Function) {
    return nextTick(fn, this)}// Execute the render function of the component to get the vNode of the component
  Vue.prototype._render = function () :VNode {
    const vm: Component = this
    /* Get the render function: 1. The user instantiates vue with the render configuration item 2. The compiler generates render */ when compiling the template 
    const { render, _parentVnode } = vm.$options

    // Standardize the scope slot
    if (_parentVnode) {
      vm.$scopedSlots = normalizeScopedSlots(
        _parentVnode.data.scopedSlots,
        vm.$slots,
        vm.$scopedSlots
      )
    }

    // set parent vnode. this allows render functions to have access
    // to the data on the placeholder node.
    vm.$vnode = _parentVnode
    // render self
    let vnode
    try {
      // There's no need to maintain a stack because all render fns are called
      // separately from one another. Nested component's render fns are called
      // when parent component is patched.
      currentRenderingInstance = vm
      // Execute the render function to get the component's vNode
      vnode = render.call(vm._renderProxy, vm.$createElement)
    } catch (e) {
      handleError(e, vm, `render`)
      // return error render result,
      // or previous vnode to prevent render error causing blank component
      /* istanbul ignore else */
      if(process.env.NODE_ENV ! = ='production' && vm.$options.renderError) {
        try {
          vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)
        } catch (e) {
          handleError(e, vm, `renderError`)
          vnode = vm._vnode
        }
      } else {
        vnode = vm._vnode
      }
    } finally {
      currentRenderingInstance = null
    }

    // If the returned vNode array contains only one node, then vNode fetches the node directly
    if (Array.isArray(vnode) && vnode.length === 1) {
      vnode = vnode[0]}// return empty vnode in case the render function errored out
    // If the render function contains multiple root nodes, an empty vNode is returned
    if(! (vnodeinstanceof VNode)) {
      if(process.env.NODE_ENV ! = ='production' && Array.isArray(vnode)) {
        warn(
          'Multiple root nodes returned from render function. Render function ' +
          'should return a single root node.',
          vm
        )
      }
      vnode = createEmptyVNode()
    }
    
    // set parent
    vnode.parent = _parentVnode
    return vnode
  }
}
Copy the code

installRenderHelpers(Vue.prototype)

// Mount some run-time tool methods on the component instance
export function installRenderHelpers (target: any) {
  target._o = markOnce
  target._n = toNumber
  target._s = toString
  // v-for
  target._l = renderList
  target._t = renderSlot
  target._q = looseEqual
  target._i = looseIndexOf
  // Render static nodes
  target._m = renderStatic
  target._f = resolveFilter
  target._k = checkKeyCodes
  target._b = bindObjectProps
  target._v = createTextVNode
  target._e = createEmptyVNode
  target._u = resolveScopedSlots
  target._g = bindObjectListeners
  target._d = bindDynamicKeys
  target._p = prependModifier
}
Copy the code

conclusion

The following instance methods were covered in the previous section:

  • vm.$set
  • vm.$delete
  • vm.$watch
  • vm.$on
  • vm.$emit
  • vm.$off
  • vm.$once
  • vm._update
  • vm.$forceUpdate
  • vm.$destroy
  • vm.$nextTick
  • vm._render

Most of these sample methods were explained and summarized in the previous article, but are not repeated here for more details

What is vm.$watch(expOrFn, callback, [options]) doing?

Observe a change in the evaluation result of an expression or function on a Vue instance.

$watch(‘obj.person.name’, callback);

For more complex expressions, use a function instead, such as vm.$watch(function(){return this.obj.person.name}, callback).

Note: If the value being observed is of type object, care should be taken when observing an object and determining whether old and new values are equal in the callback function

  • Such asArray arr, when using the rewritten7When an array method modifies an element in an array, the callback function receives the same old and new values when fired, because they point to the same reference
  • Such asThe plain object obj, when passing through similarobj.key = newValueWhen triggered, the callback function receives the same old and new values because they point to the same reference

Vm.$watch handles:

  • By setting theoptions.user = true, mark currentwatcherIt’s a userwatcher
  • Instantiate aWatcherInstance, passes when a data update is detectedwatcherTo trigger the callback and pass the old and new values as arguments to the callback
  • Returns aunwatchFunction to remove observations

What does vm.$forceUpdate() do?

To force the Vue instance to re-render, note that it only affects the instance itself and the child components that insert the slot content, not all of the child components. Internally, this is a direct call to vm._watcher.update(), essentially the watcher.update() method, which triggers the component update.

What does vm.$destroy() do?

Destroying an instance completely, cleaning up the connection between the current component instance and other instances, and untying all of its directive and event listeners triggers hooks for beforeDestroy and Destroyed.

Note: It is not officially recommended to call this method directly; it is better to use the V-if and V-for directives to control the life cycle of child components in a data-driven manner.

The detailed process is as follows:

  • If the current component has already been destroyed, return directly, such as multiple callsvm.$destroy()
  • Otherwise the callbeforeDestroyLifecycle hook, then passvm._isBeingDestroyed = trueMarks the component for destruction
  • ifvm.$parentIf the parent component exists, the current component is removed from the parent componentchildrenProperty is removed
  • through vm._watcher.teardown()Removes all associated with the current componentwatcher
  • throughvm._data.__ob__.vmCount--Delete the current component_data.__ob__The reference number
  • throughvm._isDestroyed = trueIdentifies that the component has been destroyed and then invokesdestroyedDeclare periodic function hooks
  • throughvm.$off()Removes all listeners for the current component
  • Sets the parent of the current component tonullTo disconnect the parent component

What does vm._update(vnode, hydrating) do?

PrevVnode (prevVnode, prevVnode, prevVnode, prevVnode, prevVnode, prevVnode); prevVnode (prevVnode, prevVnode); prevVnode (prevVnode, prevVnode);

What does vm._render do?

This method is an instance method used within the source code to generate a VNode,

  • To obtainrenderFunction:
    • User instantiationvueprovidesrenderConfiguration items
    • Generated when the compiler compiles the templaterenderfunction
  • callrenderFunction to getvnode
  • ifrenderThere are multiple root nodes in the functionvnode = createEmptyVNode()Generate emptyvnodenode

The articles

  • What does vUE initialization do?
  • How to understand vUE responsiveness?
  • How does vUE update asynchronously?
  • Do you really understand the Vue global Api?