vue系列 -- 生命周期

937 阅读5分钟

说 vue 的生命周期之前我们先来看个 vue 实例

vue 实例

先看一句话: el 属性,template 属性,render 渲染函数 都可以指明 vue 对象对应的 HTML 元素(DOM 对象),三者可以并存,三者的书写方式不同:

  • el 属性:对应的 HTML 元素是写在 HTML 文档上的
  • template 属性:对应的 HTML 元素直接写该属性后面,作为属性的值
  • render 函数:对应的 HTML 元素是写在 createElement 方法里面的,其中写入 data 对象里的变量不能用双括号{{}},得用 this 指向 data 对象

另外:

  • 只有 template 属性是不行的,因为 vue 对象不知道把 template 放在何处;
  • 只有 render 函数也是不行的,因为 vue 对象不知道把 render 后的结果放在何处 只有 el 属性
<body>
  <div id="app">
    <h1>{{message}} -- 我是本身写在 HTML 里的,el 会指向我</h1>
  </div>
<script>
  var vm = new Vue({
    el: '#app',
    data: {
      message: 'Vue的生命周期'
    }
  })
</script>
</body>

image.png 由图看出:vue 实例对象里的 el 属性自动指向了 id 为 app<div> 标签

既有 el 属性,又有 template 属性

<body>
  <div id="app">
    <h1>{{message}} -- 我是本身写在 HTML 里的,el 会指向我</h1>
  </div>
<script>
  var vm = new Vue({
    el: '#app',
    template: "<h1>{{message}} -- 我是 template 属性出来的 HTML</h1>", // 在vue配置项中修改的
    data: {
      message: 'Vue的生命周期'
    }
  })
</script>
</body>

image.png 由图看出:vue 实例对象里的 template 属性里的 HTML 元素代替了 el 属性指向的 <div> 标签里的元素

既有 el 属性和 template 属性,又有 render (渲染)函数

<body>
  <div id="app">
    <h1>{{message}} -- 我是本身写在 HTML 里的,el 会指向我</h1>
  </div>
<script>
  var vm = new Vue({
    el: '#app',
    template: "<h1>{{message}} -- 我是 template 属性出来的 HTML</h1>", // 在vue配置项中修改的
    data: {
      message: 'Vue的生命周期'
    },
    render:function(createElement){
      // return createElement('h1', '{{message}} -- 我是 render 函数出来的 HTML');
      // 以上写法是错误的,因为使用 JavaScript 不能使用 “Mustache” 语法 (双大括号)
      return createElement('h1', this.message + ' -- 我是 render 函数出来的 HTML');
    }
  })
</script>
</body>

image.png 由图看出:vue 实例对象里的 render 函数里写的 HTML 元素代替了 template 属性写的 HTML 元素

由此可以看出:

  1. el 属性,template 属性,render 渲染函数 都可以用来指明 vue 对象对应的 HTML 元素
  2. 而前两种方式引用 data 对象里面的变量时可以使用{{}},render 函数就得用 this 指向 data 对象然后才能使用 data 对象里面的变量
  3. 并且优先级顺序为:el < template < render

其中 el 属性是 必须的,因为它的作用就是:用来 指定 vue 要管理的 DOM 元素,可以帮助解析其中的指令、事件监听等等

生命周期图示

参考网上对官网的图示解读,让我们对 vue 的生命周期有个大概的了解:

从这张图我们可以看出:一个 vue 实例中有 8 个状态,对应 8 个钩子函数,每两个钩子函数之间对应一个生命周期,即 7 个生命周期,下面我们来一步一步拆解这些周期:

创建和挂载阶段

首先我们需要知道在 vue 这几个生命周期都大概干了什么,在每个生命周期结束后对应的状态,或者说是钩子函数能够获取到什么数据,或者说我们可以做哪些工作

借用 详解vue生命周期 里的代码,我们在控制台输出各个状态里的 el 属性、data 对象以及 data 里的 message 变量:

image.png

创建阶段

image.png

对应于官网中生命周期的图示,我们可以知道:从 beforeCreate 到 created 之间的生命周期中,vue 进行初始化事件,进行数据的观测,也就是说在 created 钩子函数里面可以拿到 data 的值了,并且进行绑定(放在 data 中的属性当值发生改变的同时,视图也会改变)。

注意:el 属性的值还是 undefined

image.png

挂载阶段

上面说 el 属性是必须的,因为它是负责挂载工作,那如果没有 el 属性会怎样

image.png

可以看到:如果没有 el 属性,vue 实例就会处于等待状态,此时虽然拿到了 data 对象,但是并没能够把,由于 el 属性对应的 HTML 元素是写在 HTML 文档里的,所以能够渲染出{{message}}出来,

所以没有这个属性 vue 会怎么处理?官网的图告诉我们答案:

image.png

vue 会调用vm.$mount(el),我们可以测试一下,运行vm.$mount("#app")来代替 el: "#app"

image.png

代码继续往下编译,成功完成预挂载和挂载,因此我们可以得出结论:

从 created 到 beforeMount 之间的生命周期中,el 属性的值不再是 undefined,而是变成了<div id="app"><h1>{{message}}</h1></div>,也就是说,在这个生命周期里面,vue 完成了把 el 属性挂载到 HTML 元素的操作,定位方式就是根据 HTML 标签里的 id 和 el 属性的值(有点像选择器)

另外,vue 实例还会判断是否还有 template 属性,就是我们在最上面说的那个,接下来进入判断:

  • 如果有 template 属性 vue 实例就会把 template 属性的值 HTML 元素写到 render 函数里面
  • 否则就把 el 属性所指向的 HTML 元素写到 render 函数里面;

经历了这些操作,vue 实例才能完成预挂载,这就对应上最上面说的【优先级顺序】了

接下来看 从 beforeMount 到 mounted 之间的生命周期 中完成了什么样的工作:

image.png

从这张图可以看出:HTML 元素还是那个 HTML 元素,区别就在于 {{message}} 被换成了 Vue的生命周期( data 对象 message 属性的值)

也就是说:在 mounted 之前 h1 中还是通过 {{message}} 进行占位的,因为此时是以 JavaScript 中的 虚拟 DOM 形式 挂载在页面上的。在 mounted 之后用真实 DOM 把虚拟 DOM 替换掉

数据更新阶段

从 mounted 到 beforeUpdate 之间的生命周期:更新了 data 值,但 view 层中对应的 DOM 元素里的数据和 data 还没同步

从 beforeUpdate 到 updated 之间的生命周期:页面重新渲染,view 层中对应的 DOM 元素里的数据和 data 保持一致

<body>
    <div id="app">
        <p>{{message}}</p>
    </div>
    <script type="text/javascript">
        var vm = new Vue({
            el: "#app",
            data: {
                message: "hello world!"
            },
            beforeUpdate () {
                console.log("此时的message已经更新,但是浏览器页面上的数据没有发生变化",this.message)
                debugger // 修改数据之后,添加断点
            },
            updated () {
                console.log("更新结束,页面数据已更新")
            }
        })
        debugger // 在修改数据之前,添加断点
        vm.message = '你好,Vue生命周期的运行阶段'
    </script>
</body>

修改数据之后,添加断点,页面上的数据和当前 message 不一致:

值得注意的是:如果当前页面有挂载子组件,子组件更新时它并不能保证全部的子组件也重绘了。如果你想确定是否整个 DOM 都更新,可以使用 vm.$nextTick() 来代替 mounted 钩子函数

image.png

会得到一个 Promise 对象,对该对象使用 then、catch、await 等方法都可以获知是否整个视图都重新渲染完毕

另外,vm.$nextTick() 方法也可以用来区分不同的数据更新,针对每一个变化做不同的处理

销毁阶段

从 updated 到 beforeDestroy 之间的生命周期:进入销毁阶段,到了 beforeDestroy 状态:虽然 Vue的生命周期进入了销毁阶段,但是实例上的各种数据还出于可用状态

从 beforeDestroy 到 destroyed 之间的生命周期:完成数据销毁,到了 destroyed 状态:组件已经全部销毁,Vue实例已经被销毁,Vue中的任何数据都不可用

总结(7 个生命周期干了什么)

  1. 从 beforeCreate 到 created 之间的生命周期:初始化 data 对象 和 Events 事件
  2. 从 created 到 beforeMount 之间的生命周期:完成 HTML 元素(DOM 元素) 的预挂载,el 属性不再是 undefined 了,其值被 虚拟 DOM 替换
  3. 从 beforeMount 到 mounted 之间的生命周期:完成 HTML 元素(DOM 元素) 的挂载,把 虚拟 DOM 替换成 真实 DOM(简单来说就是把{{}}里面的变量换成它在 data 对象里面对应的值)
  4. 从 mounted 到 beforeUpdate 之间的生命周期:监听到数据变化,更新 data 值,但 view 层数据未更新
  5. 从 beforeUpdate 到 updated 之间的生命周期:重新渲染页面,使得 view 层中对应 DOM 元素的数据和 data 值保持一致
  6. 从 updated 到 beforeDestroy 之间的生命周期:进入销毁阶段,但数据仍然可以用
  7. 从 beforeDestroy 到 destroyed 之间的生命周期:完成数据销毁

各个状态(钩子函数)可以干什么事

  • created 钩子函数:可以获取 data 值,并且可以进行异步 AJAX 请求
  • mounted 钩子函数:可以获取真实的 DOM 元素,当需要操作 dom 的时候执行,可以配合$.nextTick 使用进行单一事件对数据的更新后更新 dom
  • beforeUpdate 钩子函数:可以用 watch 监听数据变化,因为还没有挂载到页面上,因此我们可以做更新数据前的最后修改
  • updated 钩子函数:可以获取到更新后的 DOM 元素

注意事项

所有的钩子函数里面的 this 全部指向 vue 实例。所以所有的钩子函数后面都必须跟普通函数而不是箭头函数,因为箭头函数里面没有 this,所以会向上级作用域查找,因此 this 指向可能变成 window 之类其他的对象,导致会报一些 Uncaught TypeError: Cannot read property of undefinedUncaught TypeError: this.myMethod is not a function之类的错误。

参考文章