vue核心面试题(vue组件的生命周期)

概念:

一、生命周期钩子在什么时候调用

1.beforeCreate 在实例初始化之后,数据观测(data observer) 之前被调用。(在文件init.js中)

2.created 实例已经创建完成之后被调用。在这一步,实例已完成以下的配置:数据观测(data observer),属性和方法的运算,watch/event 事件回调。这里没有$el ,创建完成后还没渲染到页面上。(在文件init.js中)

3.beforeMount 在挂载开始之前被调用:相关的 render 函数首次被调用。 渲染之前对template中的页面进行求值。(在文件lifecycle.js中)

4.mounted el被新创建的vm.$el 替换,并挂载到实例上去之后调用该钩子。当前页面已经挂载好了。(在文件lifecycle.js中)

5.beforeUpdate 当watcher数据更新时调用,发生在虚拟 DOM 重新渲染和打补丁之前。 (核心逻辑dom diff)(在文件lifecycle.js中)

6.updated 在数据更新之后导致虚拟 DOM 重新渲染和打补丁,在这之后会调用该钩子。(在文件scheduler.js中)

7.beforeDestroy 实例销毁之前调用。在这一步,实例仍然完全可用。 用户可以自己调用这个方法(在文件lifecycle.js中)

8.destroyed Vue 实例销毁后调用。调用后, Vue 实例指示的所有东西都会解绑定,所有的事件 监听器会被移除,所有的子实例也会被销毁。 该钩子在服务器端渲染期间不被调用。(在文件lifecycle.js中)

二、生命周期钩子内部可以做什么事

1.created 实例已经创建完成,因为它是早触发的原因可以进行一些数据,资源的请求。

2.mounted 实例已经挂载完成,拿到了vue实例对应的真是dom元素了,可以进行一些DOM操作。

3.beforeUpdate 可以在这个钩子中进一步地更改状态,这不会触发附加的重渲染过程。

4.updated 可以执行依赖于 DOM 的操作。然而在大多数情况下,你应该避免在此期间更改状态, 因为这可能会导致更新无限循环。 该钩子在服务器端渲染期间不被调用。 

5.beforeDestroy 可以在组件销毁之前进行一些优化操作,比如关闭定时器,关闭监听,用来销毁一些离开这个页面不愿理还进行的操作。

三、生命周期函数执行顺序

beforeCreate ->created ->beforeMount ->mounted->beforeUpdate ->updated ->beforeDestroy ->destroyed

四、生命周期图

五、源码

1.new vue(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方法(这儿就是vue初始化的全部逻辑)
stateMixin(Vue) // 初始化$set $delete $watch
eventsMixin(Vue) // 初始化vue中的$on $off $once $emit事件(发布订阅者模式)
lifecycleMixin(Vue) // 初始化_update方法,一会更新的时候会调此方法,生命周期的混合
renderMixin(Vue) // 初始化_render方法

2._init(src/core/instance/init.js)

    initLifecycle(vm) // 初始化组件的父子关系
    initEvents(vm) // 初始组件事件
    initRender(vm) // 初始化slot及$createElement方法(渲染虚拟节点和做插槽会用到)
    callHook(vm, 'beforeCreate') // 执行beforeCreate钩子
    initInjections(vm) // 初始化inject,父子跨组件通讯的方法
    initState(vm) // 初始化状态,包括data,methods,watch,computed,Props
    initProvide(vm) // 解析Provide
    callHook(vm, 'created')

3.src/platforms/web/entry-runtime-with-compiler.js中的$mount

  if (!options.render) { // 判断是否有render
    let template = options.template
    if (template) { // 判断是否有template
      if (typeof template === 'string') {
        if (template.charAt(0) === '#') {
          template = idToTemplate(template)
          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) // 没有template,使用外部模板编译到render函数中
    }
    if (template) {
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile')
      }
      // 如果有tempalte,执行compileToFunctions,将模板编译到render函数中
      const { render, staticRenderFns } = compileToFunctions(template, {
        outputSourceRange: process.env.NODE_ENV !== 'production',
        shouldDecodeNewlines,
        shouldDecodeNewlinesForHref,
        delimiters: options.delimiters,
        comments: options.comments
      }, this)
      options.render = render
      options.staticRenderFns = staticRenderFns
      if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
        mark('compile end')
        measure(`vue ${this._name} compile`, 'compile', 'compile end')
      }
    }
  }
  return mount.call(this, el, hydrating) // 最后执行了原来的$mount方法

4.src/platforms/web/runtime/index.js中的$mount

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

5.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 !== 'production') {
      if ((vm.$options.template && vm.$options.template.charAt(0) !== '#') ||
        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 {
        warn(
          'Failed to mount component: template or render function not defined.',
          vm
        )
      }
    }
  }
  callHook(vm, 'beforeMount') // 挂载前
  let updateComponent
  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)
    }
  }
  new Watcher(vm, updateComponent, noop, { // 渲染watcher
    before () {
      if (vm._isMounted && !vm._isDestroyed) {
        callHook(vm, 'beforeUpdate')
      }
    }
  }, true /* isRenderWatcher */)
  hydrating = false
  if (vm.$vnode == null) { // 如果当前没渲染过,并且已经挂载完了,会调用一个mounted方法
    vm._isMounted = true
    callHook(vm, 'mounted')
  }
  return vm
}

6.flushSchedulerQueue (src/core/observer/scheduler.js)

function flushSchedulerQueue () {
  for (index = 0; index < queue.length; index++) {
    watcher = queue[index]
    if (watcher.before) {
      watcher.before() // 触发一个before方法
    }
  }
  callUpdatedHooks(updatedQueue) // 更新完成后会调用updated钩子
}

7.callUpdatedHooks (src/core/observer/scheduler.js)

function callUpdatedHooks (queue) {
  let i = queue.length
  while (i--) {
    const watcher = queue[i]
    const vm = watcher.vm
    if (vm._watcher === watcher && vm._isMounted && !vm._isDestroyed) {
      callHook(vm, 'updated') // 执行updated
    }
  }
}

8.$destroy(src/core/instance/lifecycle.js)

  Vue.prototype.$destroy = function () {
    const vm: Component = this
    if (vm._isBeingDestroyed) {
      return
    }
    callHook(vm, 'beforeDestroy') // 执行beforeDestroy
    vm._isBeingDestroyed = true
    const parent = vm.$parent//把子组件销毁
    if (parent && !parent._isBeingDestroyed && !vm.$options.abstract) {
      remove(parent.$children, vm)
    }
    if (vm._watcher) { // 清楚watcher
      vm._watcher.teardown()
    }
    let i = vm._watchers.length
    while (i--) {
      vm._watchers[i].teardown()
    }
    if (vm._data.__ob__) { // 清楚数据
      vm._data.__ob__.vmCount--
    }
    vm._isDestroyed = true // 这个时候销毁就完成了
    vm.__patch__(vm._vnode, null)
    callHook(vm, 'destroyed') // 执行destroyed
    vm.$off()
    if (vm.$el) { // 将el置空
      vm.$el.__vue__ = null
    }
    if (vm.$vnode) {// 将vnode置空
      vm.$vnode.parent = null
    }

9.callHook ,就是让里面的函数依次执行

export function callHook (vm: Component, hook: string) {
  pushTarget()
  const handlers = vm.$options[hook]
  const info = `${hook} hook`
  if (handlers) {
    for (let i = 0, j = handlers.length; i < j; i++) {
      invokeWithErrorHandling(handlers[i], vm, null, vm, info)
    }
  }
  if (vm._hasHookEvent) {
    vm.$emit('hook:' + hook)
  }
  popTarget()
}

 

六、总结

        在vue创建第一步执行的是new Vue()创建vue实例,在vue类中调用了_init方法,在调用vue之前会在它的原型上扩展了五个方法分别是:initMixin,stateMixin,eventsMixin,lifecycleMixin,renderMixin,在_init方法中初始化了事件和生命周期(initLifecycle),_init中核心的八个方法是:initLifecycle(vm), initEvents(vm),initRender(vm),callHook(vm, 'beforeCreate'),initInjections(vm) , initState(vm), initProvide(vm) ,callHook(vm, 'created'),在init方法中执行了生命周期的beforeCreate和created钩子,在beforeCreate时候拿不到任何的状态,data和methods的数据还没有初始化。在creatd的时候就可以在拿到data和methods等中的数据进行操作了,一般数据请求在这个里面操作。在init方法中会判断是否有el属性,如果有就会调用$mount方法,$mount在vue中定义有两种模式,一种叫运行时runtime,这个不能编译模板,另一种是带编译版本的runtime-with-comilper,带编译版本的就支持用户写template。这个地方使用的就是带编译版本的$mount,在src/platforms/web/entry-runtime-with-compiler.js中,基于原来的$mount进行扩展,可以叫函数劫持,在这儿处理模板,最后会调用原来的mount方法。

       在mount方法中会先判断是否有render方法,如果用户写了一个render函数会最先生效,如果没有render函数才会看这些东西有没有模板,如果有template会调用compileToFunctions将它编译到render函数中,如果没有就会掉用getOuterHTML使用外部模板编译。

      这个时候都解析好了,模板也有了,就需要将组件进行挂载,变成一个真实的dom,在src/platforms/web/runtime/index.js中的是最初始的$mount,在这个里面调用了mounComponent方法(mounComponent在src/core/instance/lifecycle.js中),在这个方法中执行了beforeMount, 在这个之后执行了new Watcher是个渲染watcher,每次数据一变化,watcher就会执行。如果当前没渲染过,并且已经挂载完了,会调用一个mounted方法。

      watcher更新时,在flushSchedulerQueue 方法中,如果watcher中有before方法,就会执行beforeUpdate 方法,在渲染完成之后,再去调用callUpdatedHooks(updateQueue)更新完成,在这个里面就会调用updated方法。

      在用户调用$destroy方法会进行销毁,在$destroy中一开始就会调用beforeDestroy方法,然后将当前watcher移除掉,把数据清空,将子组件进行销毁,然后就会实行destroyed钩子,告诉我们当前已经销毁完成了。而且移除一些事件监听,将el和vnode置成空。

 

 

 

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值