Home  >  Article  >  Web Front-end  >  Vue’s life cycle and source code implementation (code)

Vue’s life cycle and source code implementation (code)

不言
不言Original
2018-09-07 16:57:482227browse

The content this article brings to you is about the life cycle and source code implementation (code) of Vue. It has certain reference value. Friends in need can refer to it. I hope it will be helpful to you.

Through learning, we have learned all the basic syntax of Vue, including:

1, {{Mustache}} syntax
2. v-if, v-else, v-else-if, v-show
3. v-for
4. v-bind
5. v-model
6. v-on

If you have already kept these grammars in mind, then please continue reading. If you are not very proficient in these grammars, then I hope you will review the previous ones. Content.

In this chapter we learn Vue’s life cycle. Let’s first take a look at the definition of Vue’s life cycle.

Each Vue instance must go through a series of initialization processes when it is created - for example, you need to set up data monitoring, compile templates, mount the instance to the DOM, and update the DOM when the data changes, etc. At the same time, some functions called lifecycle hooks will also be run during this process, which gives users the opportunity to add their own code at different stages.

This is the description information provided on the Vue official website. To put it simply: In the process of Vue from creating an instance to finally completely destroying it, a series of methods will be executed to correspond to the current Vue Status, we call these methods: life cycle hook. Let's take a look at the following life cycle diagram:

Vue’s life cycle and source code implementation (code)

In the above diagram, a total of 8 life cycle hook functions are displayed. These 8 This function describes the entire running cycle of Vue. As of now, the Vue version is -2.5.16. There are a total of 11 declaration cycle hooks in Vue. In addition to the 8 just now, there are 3 life cycle hooks about components. Let's take a look at all hook function explanations, and with the above diagram, we can better understand Vue's running cycle.

1. beforeCreate: Called after instance initialization and before data observation (data observer) and event/watcher event configuration.
2. created: Called immediately after the instance is created. At this step, the instance has completed the following configuration: data observer, operations on properties and methods, and watch/event event callbacks. However, the mount phase has not started yet and the $el attribute is currently not visible.
3. beforeMount: Called before mounting: the related render function is called for the first time.
4. mounted: el is replaced by the newly created vm.$el, and the hook is called after being mounted to the instance. If the root instance mounts an element in the document, vm.$el is also in the document when mounted is called (PS: Note that mounted does not promise that all sub-components will also be mounted together. If If you want to wait until the entire view is rendered, you can replace mounted with vm.$nextTick :). vm.$nextTick will be explained in detail in the following chapters. Everyone needs to know this thing here.
5. beforeUpdate: Called when the data is updated, which occurs before the virtual DOM is patched. This is suitable for accessing the existing DOM before updating, such as manually removing an added event listener.
6. updated: This hook will be called after the virtual DOM is re-rendered and patched due to data changes. When this hook is called, the component DOM has been updated, so you can now perform operations that depend on the DOM. In most cases, however, you should avoid changing state during this period. If you want to respond to state changes, it is usually best to use computed properties or watchers instead (PS: Computed properties and watchers will be introduced in later chapters).
7. activated: called when the keep-alive component is activated (PS: related to the component, keep-alive will be introduced to you when explaining the component).
8. deactivated: called when the keep-alive component is deactivated (PS: related to the component, keep-alive will be introduced to you when explaining the component).
9. beforeDestroy: Called before the instance is destroyed. At this step, the instance is still fully available.
10. destroyed: Called after the Vue instance is destroyed. When called, everything pointed to by the Vue instance will be unbound, all event listeners will be removed, and all child instances will be destroyed.
11. errorCaptured (new in 2.5.0): Called when capturing an error from a descendant component. This hook receives three parameters: the error object, the component instance where the error occurred, and a string containing information about the source of the error. This hook can return false to prevent the error from propagating further upwards.

These are all the life cycle hooks in Vue (2.5.16). To make it easier for everyone to understand, let’s take a look. In the Vue code, create to ## from How #Destruction is implemented. You can click here to download the latest code of Vue.

Let’s take a brief look at the basic structure of Vue source code.

.
├── BACKERS.md ├── LICENSE
├── README.md├── benchmarks
├── dist
├── examples
├── flow
├── node_modules
├── package.json├── packages
├── scripts
├── src
├── test
├── types
└── yarn.lock

This is the first-level directory after downloading the code. The dist folder is the compiled code for Vue. The Vue.js files we usually introduce are all here, Vue uses flow as a JavaScript static type checking tool. The relevant codes are under the flow folder. Under the scripts folder are the relevant configurations for code construction. Vue mainly uses Rollup for code construction. Under the src folder are all Vue source codes. We will not describe too much other content here, but focus on our theme, How Vue’s declaration cycle code is implemented, let’s take a look at the src folder.

.
├── compiler :Vue编译相关
├── core    :Vue的核心代码
├── platforms   :web/weex平台支持,入口文件
├── server  :服务端
├── sfc :解析.vue文件
└── shared  :公共代码

This is the directory structure under our src folder, and the place where our Vue is generated is /src/core/instance/index. in 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)
}

We can see: Vue is a method, a constructor implemented using Function, so we can only create instances of Vue through new. Then initialize Vue through the _init method of the Vue instance. _init is a prototype attribute implemented by Vue through prototype. Let's take a look at his _init method implementation.

In the /src/core/instance/init.js folder, Vue implements the _init method

Vue.prototype._init = function (options?: Object) {    ...
    // expose real self
    vm._self = vm
    initLifecycle(vm)
    initEvents(vm)
    initRender(vm)
    callHook(vm, 'beforeCreate')
    initInjections(vm) // resolve injections before data/props
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created')    ...
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
 }

I mainly focus on it For the code related to the life cycle, we can see that Vue first calls the three methods initLifecycle(vm), initEvents(vm), and initRender(vm) for initializing life cycle and events , rendering function , these processes occur during the Vue initialization process (_init method) , and before calling the beforeCreate hook .

Then Vue calls the hook function (hook) through the callHook (vm: Component, hook: string) method, which receives vm (Vue instance Object), hook (hook function name) To and from execution Life cycle function. In Vue, almost all hook (except errorCaptured) function execution is called through callHook (vm: Component, hook: string). Let's take a look at the code of callHook, under /src/core/instance/lifecycle.js:

export function callHook (vm: Component, hook: string) {
  // #7573 disable dep collection when invoking lifecycle hooks
  pushTarget()  const handlers = vm.$options[hook]  
  if (handlers) {    
  for (let i = 0, j = handlers.length; i < j; i++) {      
  try {
        handlers[i].call(vm)
      } catch (e) {
        handleError(e, vm, `${hook} hook`)
      }
    }
  }  if (vm._hasHookEvent) {
    vm.$emit(&#39;hook:&#39; + hook)
  }
  popTarget()
}

Its logic is also very simple, according to the incoming The hook gets the corresponding callback function array from the instance (LIFECYCLE_HOOKS under /packages/vue-template-compiler/browser.js), and then Facilitate execution.

Then after initializing life cycle, event, rendering function, the beforeCreate hook is called. At this time: We have no way to get data, props and other data.

After calling the beforeCreate hook, Vue calls initInjections(vm), initState(vm), initProvide(vm)These three methods are used for initializationdata, props, watcher, etc., after these initialization executions are completed, the created hook function is called. At this time: We can already obtain data, props Waiting for data, but Vue has not started rendering the DOM, so we cannot access the DOM yet (PS: We can access it through vm.$nextTick, in the following We will explain it in detail in the following chapters).

After calling the created hook, Vue starts to mount the DOM and executes vm.$mount(vm.$options. el), DOM mounting in Vue is implemented through the prototype method Vue.prototype.$mount. Vue.prototype.$mountThe declaration of the prototype method is in /src/platforms/web/entry-runtime-with-compiler.js. Let’s take a look at the implementation of this code:

const mount = Vue.prototype.$mount
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 !== &#39;production&#39; && 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 === &#39;string&#39;) {        
    if (template.charAt(0) === &#39;#&#39;) {
          template = idToTemplate(template)          
          /* istanbul ignore if */
          if (process.env.NODE_ENV !== &#39;production&#39; && !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 !== &#39;production&#39;) {
          warn(&#39;invalid template option:&#39; + template, this)
        }        return this
      }
    } else if (el) {
      template = getOuterHTML(el)
    }    if (template) {      
    /* istanbul ignore if */
      if (process.env.NODE_ENV !== &#39;production&#39; && config.performance && mark) {
        mark(&#39;compile&#39;)
      }      
      const { render, staticRenderFns } = compileToFunctions(template, {
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
      }, this)
      options.render = render
      options.staticRenderFns = staticRenderFns      
      /* istanbul ignore if */
      if (process.env.NODE_ENV !== &#39;production&#39; && config.performance && mark) {
        mark(&#39;compile end&#39;)
        measure(`vue ${this._name} compile`, &#39;compile&#39;, &#39;compile end&#39;)
      }
    }
  }  return mount.call(this, el, hydrating)
}
/**
 * Get outerHTML of elements, taking care
 * of SVG elements in IE as well.
 */function getOuterHTML (el: Element): string {
  if (el.outerHTML) {    
  return el.outerHTML
  } else {    
  const container = document.createElement(&#39;p&#39;)
    container.appendChild(el.cloneNode(true))    
    return container.innerHTML
  }
}

The main function of this part of the code: is to parse the template template. As can be seen from the above code, el is not allowed to be mounted on root tags such as body and html. Then determine whether there is a render function -> if (!options.render) {...}, and then determine whether there is a template, template can be string type id, DOM node. If not, el will be parsed as template. It can be seen from the above code that whether we use single file component (.Vue) or through el, template attributes, it will eventually pass renderFunction form to parse the entire template.

It can be seen from our diagram that after the template parsing is completed, the beforeMount hook will be called. So where is this beforeMount hook called? Let's look down. $mountThe prototype method has a reusable design. Under /src/platforms/web/runtime/index.js, there is such a piece of code

// public mount method
Vue.prototype.$mount = function (
  el?: string | Element,
  hydrating?: boolean
): Component {
  el = el && inBrowser ? query(el) : undefined
  return mountComponent(this, el, hydrating)
}

这是一个公共的挂载方法,目的是为了被runtime only版本的Vue直接使用,它调用了mountComponent方法。我们看一下mountComponent方法的实现,实现在/src/core/instance/lifecycle.js下。

export function mountComponent (
  vm: Component,
  el: ?Element,
  hydrating?: boolean
): Component {
  vm.$el = el  
  if (!vm.$options.render) {
    vm.$options.render = createEmptyVNode    
    if (process.env.NODE_ENV !== &#39;production&#39;) {      
    /* istanbul ignore if */
      if ((vm.$options.template && vm.$options.template.charAt(0) !== &#39;#&#39;) ||
        vm.$options.el || el) {
        warn(          
        &#39;You are using the runtime-only build of Vue where the template &#39; +          
        &#39;compiler is not available. Either pre-compile the templates into &#39; +          
        &#39;render functions, or use the compiler-included build.&#39;,
          vm
        )
      } else {
        warn(          
        &#39;Failed to mount component: template or render function not defined.&#39;,
          vm
        )
      }
    }
  }
  callHook(vm, &#39;beforeMount&#39;)

  let updateComponent  
  /* istanbul ignore if */
  if (process.env.NODE_ENV !== &#39;production&#39; && 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)
    }
  }  
  // we set this to vm._watcher inside the watcher&#39;s constructor
  // since the watcher&#39;s initial patch may call $forceUpdate (e.g. inside child
  // component&#39;s mounted hook), which relies on vm._watcher being already defined
  new Watcher(vm, updateComponent, noop, null, true /* isRenderWatcher */)
  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, &#39;mounted&#39;)
  }  
  return vm
}

由上面的代码可以看出在执行vm._render()之前,调用了callHook(vm, 'beforeMount'),这个时候相关的 render 函数首次被调用,调用完成之后,执行了callHook(vm, 'mounted')方法,标记着el 被新创建的 vm.$el 替换,并被挂载到实例上。

然后就进入了我们页面正常交互的时间,也就是beforeUpdateupdated这两个回调钩子的执行时机。这两个钩子函数是在数据更新的时候进行回调的函数,Vue在/src/core/instance/lifecycle.js文件下有一个_update的原型声明:

Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
    const vm: Component = this    
    if (vm._isMounted) {
      callHook(vm, &#39;beforeUpdate&#39;)
    }    const prevEl = vm.$el
    const prevVnode = vm._vnode    
    const prevActiveInstance = activeInstance
    activeInstance = vm
    vm._vnode = vnode    
    // Vue.prototype.__patch__ is injected in entry points
    // based on the rendering backend used.
    if (!prevVnode) {      
    // initial render
      vm.$el = vm.__patch__(
        vm.$el, vnode, hydrating, false 
        /* removeOnly */,
        vm.$options._parentElm,
        vm.$options._refElm
      )      
      // no need for the ref nodes after initial patch
      // this prevents keeping a detached DOM tree in memory (#5851)
      vm.$options._parentElm = vm.$options._refElm = null
    } else {      
    // updates
      vm.$el = vm.__patch__(prevVnode, vnode)
    }
    activeInstance = prevActiveInstance    
    // 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&#39;s updated hook.
  }

我们可以看到在如果_isMountedture的话(DOM已经被挂载)则会调用callHook(vm, 'beforeUpdate')方法,然后会对虚拟DOM进行重新渲染。然后在/src/core/observer/scheduler.js下的flushSchedulerQueue()函数中渲染DOM,在渲染完成调用callHook(vm, 'updated'),代码如下:。

/**
 * Flush both queues and run the watchers.
 */function flushSchedulerQueue () { ...
  callUpdatedHooks(updatedQueue) ...}function callUpdatedHooks (queue) {
  let i = queue.length  while (i--) {
    const watcher = queue[i]
    const vm = watcher.vm    
    if (vm._watcher === watcher && vm._isMounted) {
      callHook(vm, &#39;updated&#39;)
    }
  }
}

当Vue实例需要进行销毁的时候回调beforeDestroy 、destroyed这两个函数钩子,它们的实现是在/src/core/instance/lifecycle.js下的Vue.prototype.$destroy中:

  Vue.prototype.$destroy = function () {
    const vm: Component = this    
    if (vm._isBeingDestroyed) {      
    return
    }
    callHook(vm, &#39;beforeDestroy&#39;)
    vm._isBeingDestroyed = true
    // remove self from parent
    const parent = vm.$parent
    if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
      remove(parent.$children, vm)
    }    
    // teardown watchers
    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.
    if (vm._data.__ob__) {
      vm._data.__ob__.vmCount--
    }    
    // call the last hook...
    vm._isDestroyed = true
    // invoke destroy hooks on current rendered tree
    vm.__patch__(vm._vnode, null)    
    // fire destroyed hook
    callHook(vm, &#39;destroyed&#39;)    
    // turn off all instance listeners.
    vm.$off()    
    // remove __vue__ reference
    if (vm.$el) {
      vm.$el.__vue__ = null
    }    
    // release circular reference (#6759)
    if (vm.$vnode) {
      vm.$vnode.parent = null
    }
  }

$destroy这个原型函数中,执行了Vue的销毁操作,我们可以看到在执行销毁操作之前调用了callHook(vm, 'beforeDestroy'),然后执行了一系列的销毁操作,包括删除掉所有的自身(self)、_watcher、数据引用等等,删除完成之后调用callHook(vm, 'destroyed')

截止到这里,整个Vue生命周期图示中的所有生命周期钩子都已经被执行完成了。那么剩下的activated、deactivated、errorCaptured这三个钩子函数是在何时被执行的呢?我们知道这三个函数都是针对于组件(component)的钩子函数。其中activated、deactivated这两个钩子函数分别是在keep-alive 组件激活和停用之后回调的,它们不牵扯到整个Vue的生命周期之中activated、deactivated这两个钩子函数的实现代码都在/src/core/instance/lifecycle.js下:

export function activateChildComponent (vm: Component, direct?: boolean) {
  if (direct) {
    vm._directInactive = false
    if (isInInactiveTree(vm)) {      
    return
    }
  } else if (vm._directInactive) {    
  return
  }  if (vm._inactive || vm._inactive === null) {
    vm._inactive = false
    for (let i = 0; i < vm.$children.length; i++) {
      activateChildComponent(vm.$children[i])
    }
    callHook(vm, &#39;activated&#39;)
  }
}

export function deactivateChildComponent (vm: Component, direct?: boolean) {
  if (direct) {
    vm._directInactive = true
    if (isInInactiveTree(vm)) {      return
    }
  }  if (!vm._inactive) {
    vm._inactive = true
    for (let i = 0; i < vm.$children.length; i++) {
      deactivateChildComponent(vm.$children[i])
    }
    callHook(vm, &#39;deactivated&#39;)
  }
}

而对于errorCaptured来说,它是在2.5.0之后新增的一个钩子函数,它的代码在/src/core/util/error.js中:

export function handleError (err: Error, vm: any, info: string) {
  if (vm) {    let cur = vm    while ((cur = cur.$parent)) {      
  const hooks = cur.$options.errorCaptured      
  if (hooks) {        
  for (let i = 0; i < hooks.length; i++) {          
  try {            
  const capture = hooks[i].call(cur, err, vm, info) === false
            if (capture) return
          } catch (e) {
            globalHandleError(e, cur, &#39;errorCaptured hook&#39;)
          }
        }
      }
      
    }
  }
  globalHandleError(err, vm, info)
}function globalHandleError (err, vm, info) {
  if (config.errorHandler) {    
  try {      
  return config.errorHandler.call(null, err, vm, info)
    } catch (e) {
      logError(e, null, &#39;config.errorHandler&#39;)
    }
  }
  logError(err, vm, info)
}function logError (err, vm, info) {
  if (process.env.NODE_ENV !== &#39;production&#39;) {
    warn(`Error in ${info}: "${err.toString()}"`, vm)
  }  /* istanbul ignore else */
  if ((inBrowser || inWeex) && typeof console !== &#39;undefined&#39;) {
    console.error(err)
  } else {    
  throw err
  }
}

他是唯一一个没有通过callHook方法来执行的钩子函数,而是直接通过遍历cur(vm).$options.errorCaptured,来执行config.errorHandler.call(null, err, vm, info)的钩子函数。整个逻辑的结构与callHook使非常类似的。

截止到目前Vue中所有的生命周期钩子我们都已经介绍完成了,其中涉及到了一些源码的基础,是因为我觉得配合源码来一起看的话,会对整个Vue的运行过程有个更好的理解。大家一定要下载下来Vue的源代码,对照着我们的讲解来走一遍这个流程。

相关推荐:

vue生命周期、vue实例、模板语法

图概PHP生命周期,PHP生命周期

The above is the detailed content of Vue’s life cycle and source code implementation (code). For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn