Vue 实例的生命周期

本文深入剖析Vue实例的生命周期,从创建到销毁的各个阶段,包括beforeCreate、created、beforeMount、mounted、beforeUpdate、updated、beforeDestroy和destroyed,详细解释各阶段的功能与作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前面的话

Vue实例在创建时有一系列的初始化步骤,例如建立数据观察,编译模板,创建数据绑定等。这篇文章介绍Vue实例的生命周期。

图解

在这里插入图片描述
vue生命周期总共分为8个阶段: 创建前/后,载入前/后,更新前/后, 销毁前/后。

创建前/后

在这里插入图片描述
new Vue实例之后会调用init 方法,init方法代码如下:

Vue.prototype._init = function (options) {
  const vm = this
  /*合并option配置*/
  if (options && options._isComponent) {
    initInternalComponent(vm, options)
  } else {
    vm.$options = mergeOptions(
      resolveConstructorOptions(vm.constructor),
      options || {},
      vm
    )
  }
  ...
  // 一系列初始化
  initLifecycle(vm)
  initEvents(vm)
  initRender(vm)
  callHook(vm, 'beforeCreate')//调用beforeCreate钩子
  initInjections(vm)
  initState(vm)
  initProvide(vm)
  callHook(vm, 'created') // 调用created钩子
  // ...
  if (vm.$options.el) {
    vm.$mount(vm.$options.el)
  }
}

在调用beforeCreate钩子函数前,调用的三个初始化函数的作用:

  • initLifecycle : 确认组件(Vue实例)的父子关系
  • initEvents: 将父组件的自定义事件传递给子组件
  • initRender: 提供将render函数转为vnode的方法

可见:在调用beforeCreate钩子函数时,不能使用props、methods、data、computed和watch等数据

在调用created钩子函数前,调用的三个初始化函数的作用:

  • initInjections: 主要作用是初始化inject,可以访问到对应的依赖

  • initState:初始化会使用到的状态,状态包括props、methods、data、computed、watch五个选项。

    export function initState(vm) {
      ...
      const opts = vm.$options
      if(opts.props) initProps(vm, opts.props)
      if(opts.methods) initMethods(vm, opts.methods)
      if(opts.data) initData(vm)
      ...
      if(opts.computed) initComputed(vm, opts.computed)
      if(opts.watch && opts.watch !== nativeWatch) {
        initWatch(vm, opts.watch)
      }
    }
    

    在initState函数中顺序调用这些函数: ininProps、initMethods、initData、initComputed、initWatch。这样初始化的结果是在data选项中可以使用props;在computed选项中可以使用data、props中的数据;wacth选项可以监听data、props、computed数据的变化。methods选项的组成是函数,在这些函数被调用时,初始哈工作已经完成,可以使用全部的数据。

  • initProvide:初始化父组件提供的provide依赖。

可见:在调用created钩子函数时,可以访问data里面的数据。

提两个问题:

  • 可以在beforeCreate钩子内通过this访问到data中定义的的变量么?

    答案:是不可以访问的,在调用beforeCreate这个钩子函数时,data中的变量还没有挂载到this上,这个时候访问时undefined。

  • methods选项内的方法可以使用箭头函数么,会造成什么样的结果?

    答案:是不可以使用箭头函数的,箭头函数中的this,是继承父级作用域中的this。在vue内部,methods内每个方法的上下文是当前的组件实例。如果使用的箭头函数,this就指向当前实例的父级作用域上的this,也就是undefined。

载入前/后

在这里插入图片描述

  • 在init函数的最后检查vm.$options上面有没有el属性,如果有的话使用vm.$mount方法挂载vm,形成数据层与视图层的联系。如果没有el属性,就自己手动vm.$mount("#app")挂载。

  • vm.$mount()在很多地方都会定义,是根据不同打包方式和平台有关的,比如这几个文件都定义了src/platform/web/entry-runtime-with-compiler.js、src/platform/web/runtime/index.js、src/platform/weex/runtime/index.js,我们的关注点在第一个文件,但在 entry-runtime-with-compiler.js 文件中会首先把 runtime/index.js 中的$mount 方法保存下来,并在最后用 call 运行:

    // src/platform/web/entry-runtime-with-compiler.js
    
    const mount = Vue.prototype.$mount    // 把原来的$mount保存下来,位于 src/platform/web/runtime/index.js
    Vue.prototype.$mount = function(
      el?: string | Element,    // 挂载的元素
      hydrating?: boolean       // 服务端渲染相关参数
    ): Component {
      el = el && query(el)
      
      const options = this.$options
      if (!options.render) {                // 如果没有定义render方法
        let template = options.template
        
        // 把获取到的template通过编译的手段转化为render函数
        if (template) {
          const { render, staticRenderFns } = compileToFunctions(template, {...}, this)
          options.render = render
        }
      }
      return mount.call(this, el, hydrating)      // 执行原来的$mount
    }
    
    

    在Vue 2.0中我们要将template、el转化为渲染render()函数,上面代码中的compileToFunctions函数的作用就是将template通过编译的手段转化为render函数。

  • 转化为render函数之后,完成挂载的代码如下:

    // src/platform/weex/runtime/index.js
    
    Vue.prototype.$mount = function (
      el?: string | Element,    // 挂载的元素
      hydrating?: boolean       // 服务端渲染相关参数
    ): Component {
      el = el && inBrowser ? query(el) : undefined        // query就是document.querySelector方法
      return mountComponent(this, el, hydrating)          // 位于core/instance/lifecycle.js
    }
    

    看一下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
      }
      callHook(vm, 'beforeMount')            // 调用beforeMount钩子
    
      // 渲染watcher,当数据更改,updateComponent作为Watcher对象的getter函数,用来依赖收集,并渲染视图
      let updateComponent
      updateComponent = () => {
        vm._update(vm._render(), hydrating)
      }
    
      // 渲染watcher, Watcher 在这里起到两个作用,一个是初始化的时候会执行回调函数
      // ,另一个是当 vm 实例中的监测的数据发生变化的时候执行回调函数
      new Watcher(vm, updateComponent, noop, {
        before () {
          if (vm._isMounted) {
            callHook(vm, 'beforeUpdate')            // 调用beforeUpdate钩子
          }
        }
      }, true /* isRenderWatcher */)
    
      // 这里注意 vm.$vnode 表示 Vue 实例的父虚拟 Node,所以它为 Null 则表示当前是根 Vue 的实例
      if (vm.$vnode == null) {
        vm._isMounted = true               // 表示这个实例已经挂载
        callHook(vm, 'mounted')            // 调用mounted钩子
      }
      return vm
    }
    

    这里可以看到,beforeMount钩子函数在render函数生成之后、开始执行挂载之前调用的。 beforeMount钩子函数执行之后,会实例化一个Watcher观察者对象。这个观察者在响应式原理中发挥巨大的作用。

  • 在完成挂载之后,会调用mounted钩子函数,此时可以对DOM进行操作。

更新前/后

在这里插入图片描述
这两个钩子函数跟数据更新有关系。

其相关代码,在实例挂载过程中有如下代码:

new Watcher(vm, updateComponent, noop, {
  before () {
    if (vm._isMounted && !vm._isDestroyed) {
      callHook(vm, 'beforeUpdate')
    }
  }
}, true)

Watcher构造函数代码:

class Watcher {
  constructor (vm,expOrFn,cb,options,isRenderWatcher) {
    this.vm = vm
    if (isRenderWatcher) {
      vm._watcher = this
    }
   
    vm._watchers.push(this)
    if (options) {
      /* 省略... */
      this.before = options.before
    }
  }
  /* 省略... */
  // 数据更新时调用,没有添加强制要求时,默认使用queueWatcher函数完成数据更新
  update () {
    if (this.lazy) {
      this.dirty = true
    } else if (this.sync) {
      this.run()
    } else {
      queueWatcher(this)
    }
  }
  /* 省略... */
}

在实例化Watcher观察者时,会将传入的before函数添加到观察者对象上。在数据更新时会执行update方法,在没有添加强制要求时,默认使用queueWatcher函数完成数据更新。

export function queueWatcher (watcher: Watcher) {
  /* 省略... */
  flushSchedulerQueue()
  /* 省略... */
}

function flushSchedulerQueue () {
  /* 省略... */
  for (index = 0; index < queue.length; index++) {
    watcher = queue[index]
    if (watcher.before) {
      watcher.before()
    }
    id = watcher.id
    has[id] = null
    // 数据更新调用run方法
    watcher.run()
    /* 省略... */
  }
  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 && !vm._isDestroyed) {
      callHook(vm, 'updated')
    }
  }
}

从上面代码看出,数据更新时通过调用观察者对象的实例方法run完成的,在调用run之前,会调用实例对象上的 before方法,从而执行beforeUpdata钩子函数;在调用run之后(数据更新完成后),通过调用callUpdatedHooks函数执行updated钩子函数

总结:在生成渲染函数之后,调用 beforeMount 钩子,接着根据渲染函数生成真实DOM并挂载,然后调用 mounted 钩子。

销毁前/后

在这里插入图片描述
调用实例方法$destroy()完全销毁一个实例。代码如下:

Vue.prototype.$destroy = function () {
  const vm = this
  if (vm._isBeingDestroyed) { return }
  callHook(vm, 'beforeDestroy')
  vm._isBeingDestroyed = true
  const parent = vm.$parent
  if (parent && !parent._isBeingDestroyed && !vm.$options.abstract){
    remove(parent.$children, vm)
  }
  if (vm._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')
  vm.$off()
  if (vm.$el) {
    vm.$el.__vue__ = null
  }
  if (vm.$vnode) {
    vm.$vnode.parent = null
  }
}
  • 先判断实例上的_isBeingDestroyed是否为true,这是实例正在被销毁的标识,为了防止重复销毁组件。在正式开始执行销毁之前,调用beforeDestroy钩子函数
  • 之后开始销毁组件:
    • 将实例从其父级实例中删除
    • 移除实例的依赖
    • 移除实例内响应式数据的引用
    • 删除子组件实例
  • 完成上述操作之后,调用destroyed钩子函数
总结
  • beforeCreate (创建前): 数据对象 data都是undefined, 还未初始化

  • created (创建后) : data数据初始化完成, el还未初始化

  • beforeMount (载入前): vue实例的$el和data都初始化了, 相关的render函数首次被调用。实例已完成以下的配置:编译模板,把data里面的数据和模板生成html。注意此时还没有挂载html到页面上。

  • mounted (载入后) :在el 被新创建的 vm.$el替换,并挂载到实例上去之后调用。实例已完成以下的配置:用上面编译好的html内容替换el属性指向的DOM对象。完成模板中的html渲染到html页面中。此过程中进行ajax交互。

  • beforeUpdate (更新前): 在数据更新之前调用,发生在虚拟DOM重新渲染和打补丁之前。

  • updated (更新后): 在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。调用时,组件DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。

  • beforeDestroy (销毁前): 在实例销毁之前调用。实例仍然完全可用。

  • destroyed (销毁后): 在实例销毁之后调用。调用后,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。

参考文章:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

crazy的蓝色梦想

如果对你有帮助,就鼓励我一下吧

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值