复习Vue11:生命周期探讨

其实在Vue实例的生命周期,主要分为三个阶段,分别为

  • 挂载(初始化相关的属性,例如watch属性,method属性)
    1、beforeCreate
    2、created
    3、beforMounte
    4、mounted
  • 更新(元素或组件的变更操作)
    1、beforUpdate
    2、updatad
  • 销毁(销毁相关属性)
    1、beforDestroy
    2、destroyed

下面我们来看一道面试题:

关于Vue的生命周期,下列哪项是不正确的?()[单选题]
A、Vue 实例从创建到销毁的过程,就是生命周期。 
B、页面首次加载会触发beforeCreate, created, beforeMount, mounted, beforeUpdate, updated。 
C、created表示完成数据观测,属性和方法的运算,初始化事件,$el属性还没有显示出来。 
DDOM渲染在mounted中就已经完成了。

分析:
选项A是没有问题的,Vue实例从创建到销毁的过程就是生命周期。
关于B选项,我们可以通过写一个程序来进行验证。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>生命周期</title>
  </head>
  <body>
    <div id="app">{ { foo}}</div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
      const vm = new Vue({ 
        el: "#app",
        data: { 
          foo: "foo",
        },
        beforeCreate() { 
          console.log("beforCreate");
        },
        created() { 
          console.log("created");
        },
        beforeMount() { 
          console.log("beforeMount");
        },
        mounted() { 
          console.log("mounted");
        },
        beforeUpdate() { 
          console.log("beforeUpdate");
        },
       updated() { 
          console.log("updated");
        },
        beforeDestroy() { 
          console.log("beforeDestroy");
        },
        destroyed() { 
          console.log("destroyed");
        },
      });
    </script>
    <script></script>
  </body>
</html>

上面的代码我们将所有的钩子函数都添加上了,我发现,当页面初次加载时所执行的函数钩子,并没有beforeUpdateupdated,所以选项B是错误的

那么beforeUpdateupdated在什么时候会执行呢?是在组件或者是元素更新的时候。

下面,我们来测试一下,看一下效果

首先添加一个“更新”按钮

  <div id="app">
      { { foo}}
      <button @click="update">更新</button>
    </div>

对应的update方法的实现:

  methods: { 
          update: function () { 
            this.foo = "hello";
          },
        },

update方法中,修改了foo属性的值。如下:点击了更新按钮

通过以上的测试,我们验证在更新元素的时候,会执行“更新”阶段的钩子函数。

下面我们测试一下,看一下“销毁阶段的钩子函数的执行”

<div id="app">
      { { foo}}
      <button @click="update">更新</button>
      <button @click="destroy">销毁</button>
    </div>

在上面的代码中增加了一个销毁的按钮,对应的destroy的方法实现如下:

	 methods: { 
          update: function () { 
            this.foo = "hello";
          },
          destroy: function () { 
            //销毁资源
            this.$destroy();
          },
        },

destroy方法中,调用了系统中的$destroy方法销毁了所有的资源,这是会触发销毁阶段的钩子函数,所以这时会输出如下:

如果我们这是去点击更新按钮,就会发现什么效果都没有了,也就是无法完成元素的更新了,因为元素已经被销毁了。

下面,我们通过官方的生命周期图来再看一下整个生命周期的流程。也是为了看一下上面 CD的选项是否正确

beforcreateVue实例初始化之后,以及事件初始化,以及组件的父子关系确定后执行该钩子函数,一般在开发中很少使用

created:在调用该方法之前,初始化会被使用到的状态,状态包括:props,methods,data,computed,watch.

而且会实现对data中属性的监听,也就是在created的时候数据已经和data属性进行了绑定。(放在data中的属性当值发生改变的时候,视图也会发生改变)。同时也会传递到组件中的数据进行校验。

所以在执行created的时候,所有的状态都初始化完成,我们也完全可以在该阶段发送异步的ajax请求,获取数据。

但是在created方法中,是无法获取到对应的$el选项,也就是无法获取Dom,所以说上题中的C的说法是正确的。

如下代码所示:

        created() { 
          console.log("created");
          console.log("el===", this.$el);// undefined
          console.log("data==", this.$data);// 可以获取数据
          console.log("foo==", this.foo);//可以获取数据
        },

created方法执行完毕后,下面会判断对象中没有el选项。如果有,继续执行下面的流程,也就是判断是否有template选项,如果没有el,则停止整个生命周期的流程,知道执行了vm.$mount(el)后,才会继续向下执行生命周期的流程

下面我们测试一下:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>生命周期</title>
  </head>
  <body>
    <!-- <div id="app">{ { foo}}
        <button @click="update">更新</button>
        <button @click="destroy">销毁</button>
    </div> -->
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
        const vm = new Vue({ 
          // el: "#app", //去掉了el选项
          data: { 
            foo: "fooData",
          },
          methods: { 
            update: function () { 
              this.foo = "hello";
            },
            destroy: function () { 
              //销毁资源
              this.$destroy();
            },
          },
          beforeCreate() { 
            console.log("beforCreate");
          },
          created() { 
            console.log("created");
            console.log("el===", this.$el);
            console.log("data==", this.$data);
            console.log("foo==", this.foo);
          },
          beforeMount() { 
            console.log("beforeMount");
          },
          mounted() { 
            console.log("mounted");
          },
          beforeUpdate() { 
            console.log("beforeUpdate");
          },
          updated() { 
            console.log("updated");
          },
          beforeDestroy() { 
            console.log("beforeDestroy");
          },
          destroyed() { 
            console.log("destroyed");
          },
        });
      </script>
    <script></script>
  </body>
</html>

在上面的代码中,我们将el选项去掉了,运行上面的代码后,我们发现执行完created方法后,整个流程就停止了。

现在我们不添加el选项,但是手动执行vm.$mount(el),也能够使暂停的生命周期进行下去。

如下代码:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>生命周期</title>
  </head>
  <body>
    <div id="app">{ { foo}}
        <button @click="update">更新</button>
        <button @click="destroy">销毁</button>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
        const vm = new Vue({ 
          // el: "#app", //去掉了el选项
          data: { 
            foo: "fooData",
          },
          methods: { 
            update: function () { 
              this.foo = "hello";
            },
            destroy: function () { 
              //销毁资源
              this.$destroy();
            },
          },
          beforeCreate() { 
            console.log("beforCreate");
          },
          created() { 
            console.log("created");
            console.log("el===", this.$el);
            console.log("data==", this.$data);
            console.log("foo==", this.foo);
          },
          beforeMount() { 
            console.log("beforeMount");
          },
          mounted() { 
            console.log("mounted");
          },
          beforeUpdate() { 
            console.log("beforeUpdate");
          },
          updated() { 
            console.log("updated");
          },
          beforeDestroy() { 
            console.log("beforeDestroy");
          },
          destroyed() { 
            console.log("destroyed");
          },
        });
        vm.$mount("#app");//添加了$mount方法
      </script>
    <script></script>
  </body>
</html>

运行上面的代码,我们可以看到,虽然vm对象中没有el参数,但是通过$mount(el)动态添加的方式,也能够使生命周期孙俪进行下去。

我们继续向下看,就是判断在对象中是否有template选项。

第一:如果Vue实力对象中有template参数选项,则将其作为模板编译成render函数,来完成渲染。

第二:如果没有template参数选项,则将外部的html作为模板编译(template),也就是说,template参数选项的优先级要比外部的html

第三:如果第一条,第二条都不具备,则报错

下面,我们看一看template的情况

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>生命周期2</title>
  </head>
  <body>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <div id="app"></div>
    <script>
      const vm = new Vue({ 
        el: "#app",
        template: "<p>Hello { {message}}</p>",
        data: { 
          message: "vue",
        },
      });
    </script>
  </body>
</html>

以上是在vue实例中添加template的情况、

那么这里有一个比较有趣的问题就是,当模板同时放在template参数选项和外部HTML中,会出现什么情况呢?

如下代码:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>生命周期2</title>
  </head>
  <body>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <div id="app">
      <p>你好</p>
    </div>
    <script>
      const vm = new Vue({ 
        el: "#app",
        template: "<p>Hello { {message}}</p>",
        data: { 
          message: "vue",
        },
      });
    </script>
  </body>
</html>

上面的代码中,我们添加了template属性,同时在外部添加了模板内容,但是最终在页面上显示的是Hello vue而不是你好。就是因为template参数的优先级比外部的HTML的优先级要高。

当然,我们在开发中,基本上都是使用外部的HTML模板形式,因为更加灵活。

在这里,还需要你再次思考一个问题,就是为什么先判断el选项,然后在判断template选项呢?

其实通过上面的总结,我们是完全总结出来的。

就是因为Vue需要通过el的选择器找到对应的template,也就是说,Vue首先通过el参数去查找对应的template,如果没有找到template参数,则到外部html中查找,找到后将模板编译成render

函数(Vue的编译实际上就是指Vue把模板编译成render函数的过程)。

下面,我们继续看一下生命周几的流程图。

接下来会触发 beforMount这个钩子函数:

在执行该钩子函数的时候,虚拟DOM已经创建完成,马上就要进行渲染了,在这里可以更改data中的数据,不会触发updated,其实在created中也是可以更改数据,也不会触发updated函数。

测试代码如下:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>生命周期</title>
  </head>
  <body>
    <div id="app">{ { foo}}
        <button @click="update">更新</button>
        <button @click="destroy">销毁</button>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
        const vm = new Vue({ 
          // el: "#app", //去掉了el选项
          data: { 
            foo: "fooData",
          },
          methods: { 
            update: function () { 
              this.foo = "hello";
            },
            destroy: function () { 
              //销毁资源
              this.$destroy();
            },
          },
          beforeCreate() { 
            console.log("beforCreate");
          },
          created() { 
            console.log("created");

          },
          beforeMount() { 
            console.log("beforeMount");
          console.log("beforeMount el===", this.$el);
          console.log("data==", this.$data);
          //this.foo = "abc"; //修改数据
          console.log("foo==", this.foo);
        },
          mounted() { 
            console.log("mounted");
          },
          beforeUpdate() { 
            console.log("beforeUpdate");
          },
          updated() { 
            console.log("updated");
          },
          beforeDestroy() { 
            console.log("beforeDestroy");
          },
          destroyed() { 
            console.log("destroyed");
          },
        });
        vm.$mount("#app");//添加了$mount方法
      </script>
    <script></script>
  </body>
</html>

通过上面的代码,我们可以获取el中的内容,同时也可以修改数据。

但是,这里需要注意的输入的el中的内容,{ {foo}}还没有真正的数据替换掉。而且对应的内容还没有挂载到页面上。

下面执行了Create VM.$el and replace "el" with it

经过这一步后,在模板中所写的{ {foo}}就会被具体的数据替换掉。

所以下面执行mounted的时候,可以看到真实的数据。同时整个组件内容已经挂载到页面中了,数据以及真实DOM都已经处理好了,可以在这里操作真实的DOM了,也就是在mounted的时候,页面已经被渲染完毕了,在这个钩子函数中,我们可以发生ajax请求.

  mounted() { 
          console.log("mounted");
          console.log("mounted el===", this.$el);
          console.log("data==", this.$data);
          console.log("foo==", this.foo);
        }

所以说,最开始的问题中,D选项:DOM渲染在mounted中就已经完成了 这就话的描述是正确的。

下面继续看生命周期的流程,如下图所示:

当整个组件挂载完成后没有可能会进行数据的修改,当Vue发现data中数据发生了变化,会除非对应组件的重新渲染,先后调用了beforUpdateupdated钩子函数。

updated之前beforUpdate之后又一个非常重要的操作就是虚拟DOM会重新构建,也就是新构建的虚拟DOM与上一次的虚拟DOM数理由diff算法进行对比之后重新渲染。

而到了updated这个方法,就表示数据已经更新完成,dom也重新render完成。

下面如果我们调用了 vm.$destroy方法后,就会销毁所有的资源。

首先会执行beforDestory这个钩子函数,这个钩子函数在实例销毁前调用,在这一步,实例仍然可用。

在该方法中,可以做一些清理的工作,比如:清除定时器等。

但是执行到destoryed钩子函数的时候,Vue实例已经被销毁,所有的事件监听会被移除,所有的子实例也会被销毁。

最后做一个简单的总结:

beforeCreate( )// 该钩子函数执行时,组件实例还未创建.
created()//组件初始化完毕,各种数据可以使用,可以使用ajax发送异步请求获取数据
beforeMounted()// 未执行渲染,更新,虚拟DOM完成,真实DOM未创建
mounted()// 初始化阶段结束,真实DOM已经创建,可以发送异步请求获取数据,也可以访问dom元素
beforeUpdate()//更新前,可用于获取更新前各种状态数据
updated()//更新后执行该钩子函数,所有的状态数据是最新的。
beforeDestroy() // 销毁前执行,可以用于一些定时器的清除。
destroyed()//组件已经销毁,事件监听器被移除,所有的子实例也会被销毁。

以上为生命周期的内容。

本文地址:https://blog.csdn.net/tinglis/article/details/114265084