【Vue/React】彻底理解vue的生命周期和钩子函数

开篇图示:vue的生命周期

我们可以到官网看一下 生命周期图
在这里插入图片描述
(Ps:看不太清的可以点上面的链接,去官网看哈~)

生命周期函数和钩子函数是干什么的?

使用vue框架,需要在合适的时机做合适的事情,了解了vue对象的生命周期和钩子函数,才能知道,哪些事情应该咋哪个函数里做
······················································································································································································———— 鲁迅

一、vue的生命周期的理解

1.人:生命周期

用人举例说明:

生命周期就是一个人的一生,此处我需要说的没有人情一点(⊙﹏⊙‖∣)

从人的出生,到成长,到工作,到死亡,就是人的一生,也叫一个人的生命周期

2. 举个栗子:对象的生命周期

在程序开发中,是要使用对象的。那么,对象的生命周期就是:从对象的创建,到使用对象,到对象的消亡就是对象的生命周期。

用人和对象进行类比(此处没有人性):

程序中的对象
人出生 New对象
人工作(ps:要人的目的就是为了工作,如果一个人不工作,不为国家做贡献,那就不是合格的人,活着没有意义) 使用对象的方法和属性(ps:new的对象的目的就是为了用它,用对象主要就是使用对象的方法和属性)
人死亡(ps:人没有用了,那就“去死吧”) 对象使用完就该消亡了(过河拆桥,不用了,那就不要了。)

3. 同理:Vue的生命周期

Vue实例,vue组件实例就是vue对象,也是对象。所以,vue的生命周期和对象的生命周期是同样的道理

二、vue生命周期经历的阶段

生命周期是有不同的阶段的,就像人一样,有幼儿期,童年期,少年期,青年期,中年期,老年期 每个阶段应该做不同的事情,但是每个人做的事情又不尽相同

Vue对象的生命周期也分不同的阶段,不同的阶段也可以做不同的事情,但是不同的vue(组件)对象在不同的阶段做的事情也不尽相同,所以,每个vue组件的代码不相同

Vue生命周期经历哪些阶段:

总体来说:初始化、运行中、销毁

详细来说:开始创建、初始化数据、编译模板、挂载Dom、渲染 → 更新 → 渲染、销毁等一系列过程

三、生命周期经历的阶段和钩子函数

  1. ↓ 实例化vue(组件)对象:new Vue()

  2. ↓ 初始化事件和生命周期 init events 和 init cycle

  3. ↓ BeforeCreated函数:

    在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。即此时vue(组件)对象被创建了,但是vue对象的属性还没有绑定,如data属性computed属性还没有绑定,即没有值。

    此时还没有数据和真实DOM。

    即:属性还没有赋值,也没有动态创建template属性对应的HTML元素(二阶段的createUI函数还没有执行)

  4. ↓ 挂载数据(属性赋值)

    包括 属性和computed的运算,

  5. ↓ Created函数:

    vue对象的属性有值了,但是DOM还没有生成,$el属性还不存在。

    此时有数据了,但是还没有真实的DOM

    即:data,computed都执行了。属性已经赋值,但没有动态创建template属性对应的HTML元素,所以,此时如果更改数据不会触发updated函数

    如果:数据的初始值就来自于后端,可以发送ajax,或者fetch请求获取数据,但是,此时不会触发updated函数

  6. ↓ 检查

    1)检查是否有el属性

    检查vue配置,即new Vue{}里面的el项是否存在,有就继续检查template项。没有则等到手动绑定调用vm.$mount(),完成了全局变量$el的绑定。

    2)检查是否有template属性

    检查配置中的template项,如果没有template进行填充被绑定区域,则被绑定区域的el对象的outerHTML(即整个#app DOM对象,包括<div id=”app” ></div>标签)都作为被填充对象替换掉填充区域

    即:如果vue对象中有 template属性,那么,template后面的HTML会替换$el对应的内容。如果有render属性,那么render就会替换template。

    即:优先关系时:render–> template -->el

  7. ↓ beforeMonute函数:

    模板编译(template)、数据挂载之前执行的钩子函数

    此时 this.$el有值,但是数据还没有挂载到页面上。即此时页面中的{{ }}还没有被替换

  8. ↓ 用vue对象的数据(属性)替换模板中的内容

  9. ↓ Monuted函数:

    模板编译完成,数据挂载完毕

    即:此时已经把数据依据挂载到了页面上,所以,页面上能够看到正确的数据了。

    一般来说,我们在此处发送异步请求(ajax,fetch,axios等),获取服务器上的数据,显示在DOM里。

  10. ↓ beforeUpdate函数:

    组件更新之前执行的函数

    数据更新了,但是,vue(组件)对象对应的dom中的内部(innerHTML)没有变,所以叫作组件更新前

  11. ↓ updated函数:

    组件更新之后执行的函数

    vue(组件)对象对应的dom中的内部(innerHTML)改变了,所以,叫作组件更新之后

  12. ↓ activated函数:keep-alive组件激活时调用

  13. ↓ deactivated函数:keep-alive组件停用时调用

  14. ↓ beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。

  15. → destroyed:Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。

实践测试一下

生命周期代码:

html:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <div id="app">
        <h1>{{ message }}</h1>
        <h1>count: {{ count }}</h1>
    </div>
    <input type="button" id="btn" value="更新">
    <input type="button" id="btn1" value="销毁">
    <script src="./plugin/vue.min.js" type="text/javascript"></script>
    <script src="./myVueDome.js" type="text/javascript"></script>
</body>

</html>

JavaScript:

(function () {
    var vm = new Vue({
        el: '#app',
        data: {
            message: '生命周期',
            age: 1
        },
        computed: {
            count: function () {
                return this.age + 1
            }
        },
        /**
         * 优先级 render -> template -> el
         */
        // template: '<p>vue对象中的template的内容</p>',
        // render: function(creatElement){
        //    return creatElement('h1','this is creatElement')
        // },
        /**
         * 初始化data数据
         */
        beforeCreate: function () {
            console.group('-----------beforeCreate创建前状态-------------')
            console.log('%c%s', 'color:red', 'el:' + this.$el); // undefined
            console.log('%c%s', 'color:red', 'data:' + this.$data); // undefined
            console.log('%c%s', 'color:red', 'count:' + this.count); // undefined
            console.log('%c%s', 'color:red', 'message:' + this.message); // undefined
            // alert('beforeCreate')
        },
        created: function () {
            console.group('-----------created创建完毕状态-------------')
            console.log('%c%s', 'color:red', 'el:' + this.$el); //  undefined
            console.log('%c%s', 'color:red', 'data:' + this.$data); // 已经初始化
            console.log('%c%s', 'color:red', 'count:' + this.count); // 已经初始化
            console.log('%c%s', 'color:red', 'message:' + this.message); // 已经初始化
            // alert('created')
        },
        /**
         * 完成了el的绑定
         */
        beforeMount: function () {
            console.group('-----------beforeMount挂载前状态-------------')
            console.log('%c%s', 'color:red', 'el:' + this.$el); //  已经初始化
            console.log(this.$el); // 拿到页面未更新数据的DOM节点
            console.log(this.$el.innerHTML); // 未挂载到页面
            console.log('%c%s', 'color:red', 'data:' + this.$data); //  已经初始化 
            console.log('%c%s', 'color:red', 'message:' + this.message); //  已经初始化
            // alert('beforeMount')
        },
        mounted: function () {
            console.group('-----------mounted挂载完毕状态-------------')
            console.log('%c%s', 'color:red', 'el:' + this.$el); //  已经初始化            
            console.log(this.$el); // 拿到页面已更新数据的DOM节点
            console.log(this.$el.innerHTML); //  已挂载到页面
            console.log('%c%s', 'color:red', 'data:' + this.$data); //  已经初始化
            console.log('%c%s', 'color:red', 'message:' + this.message); // 已经初始化
            // alert('mounted')
        },
        /**
         * vue组件更新
         */
        beforeUpdate: function () {
            console.group('-----------beforeUpdate更新前状态-------------')
            console.log('%c%s', 'color:red', 'el:' + this.$el); // 更新初始化            
            console.log(this.$el.innerHTML); // 未更新数据的innerHTML
            console.log('%c%s', 'color:red', 'data.message:' + this.$data.message); // 数据更新完毕
            console.log('%c%s', 'color:red', 'message:' + this.message); // 数据更新完毕但为赋值到页面上
            // alert('beforeUpdate')
        },
        updated: function () {
            console.group('-----------updated更新完毕状态-------------')
            console.log('%c%s', 'color:red', 'el:' + this.$el); // 更新初始化         
            console.log(this.$el.innerHTML); // 已更新数据的innerHTML
            console.log('%c%s', 'color:red', 'data:' + this.$data); // 更新初始化 
            console.log('%c%s', 'color:red', 'message:' + this.message); // 数据更新完毕并且赋值完毕
            // alert('updated')
        },
        /**
         * vue组件销毁
         */
        beforeDestroy: function () {
            console.group('-----------beforeDestroy销毁前状态-------------')
            console.log('%c%s', 'color:red', 'el:' + this.$el);
            console.log(this.$el); //
            console.log('%c%s', 'color:red', 'data:' + this.$data);
            console.log('%c%s', 'color:red', 'message:' + this.message);
        },
        destroyed: function () {
            console.group('-----------destroyed销毁完毕状态-------------')
            console.log('%c%s', 'color:red', 'el:' + this.$el);
            console.log(this.$el);
            console.log(this.$el.innerHTML);
            console.log('%c%s', 'color:red', 'data:' + this.$data);
            console.log('%c%s', 'color:red', 'message:' + this.message);
        }
    });

    document.getElementById('btn').onclick = function () {
        vm.message = '已经变了';
    }
    document.getElementById('btn1').onclick = function () {
        vm.$destroy();
    }
})()

图示讲解:

  1. 首次载入页面执行了四个生命周期函数 beforeCreate created beforeMount mounted
    在这里插入图片描述
  2. 点击 ‘更新’ 后执行两个生命周期函数 beforeUpdate updated
    在这里插入图片描述
    3.点击‘销毁’后执行两个生命周期函数 beforeDestroy destroyed
    在这里插入图片描述
    是不是清晰了很多,如果你觉得console.log会影响的理解,毕竟一加载立马看到效果,可能不那么人性化,那我们可以在每一个函数输出后加上一个阻断式的alert不就行了吗?每次点击确认后才进行下一个生命函数的执行,这样是不是更明显了,这里就不附动图了,自己尝试~

五、模拟vue的构造函数(部分框架部分)

html:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <div id="app">
        <h1>{{ message }}</h1>
        <h1>count: {{ count }}</h1>
    </div>

    <script src="./myVue.js" type="text/javascript"></script>
    <script>
        var vm = new myVue({
            el: '#app',
            data(){
                return{
                    message: 'Vue生命周期',
                    age: 1
                }
            },
            computed:{
                count: function(){
                    return this.age + 1;
                }
            },
            /**
         * 初始化data数据
         */
        beforeCreate: function () {
            console.group('-----------beforeCreate创建前状态-------------')
            console.log('%c%s', 'color:red', 'el:' + this.$el); // undefined
            console.log('%c%s', 'color:red', 'data:' + this.$data); // undefined
            console.log('%c%s', 'color:red', 'message:' + this.message); // undefined
            // alert('beforeCreate')
        },
        created: function () {
            console.group('-----------created创建完毕状态-------------')
            console.log('%c%s', 'color:red', 'el:' + this.$el); //  undefined
            console.log('%c%s', 'color:red', 'data:' + this.$data); // 已经初始化
            console.log('%c%s', 'color:red', 'message:' + this.message); // 已经初始化
            // alert('created')
        },
        /**
         * 完成了el的绑定
         */
        beforeMount: function () {
            console.group('-----------beforeMount挂载前状态-------------')
            console.log('%c%s', 'color:red', 'el:' + this.$el); //  已经初始化
            console.log(this.$el); // 拿到页面未更新数据的DOM节点
            console.log(this.$el.innerHTML); // 未挂载到页面
            console.log('%c%s', 'color:red', 'data:' + this.$data); //  已经初始化 
            console.log('%c%s', 'color:red', 'message:' + this.message); //  已经初始化
            // alert('beforeMount')
        },
        mounted: function () {
            console.group('-----------mounted挂载完毕状态-------------')
            console.log('%c%s', 'color:red', 'el:' + this.$el); //  已经初始化            
            console.log(this.$el); // 拿到页面已更新数据的DOM节点
            console.log(this.$el.innerHTML); //  已挂载到页面
            console.log('%c%s', 'color:red', 'data:' + this.$data); //  已经初始化
            console.log('%c%s', 'color:red', 'message:' + this.message); // 已经初始化
            // alert('mounted')
        }
        })
    </script>
</body>

</html>

javascript:

    class myVue{
        constructor(obj){
            // 1 设置默认值
            let defaultObj = {
                data: null,
                computed: null,
                watch: null,
                beforeCreate: function(){

                },
                created: function(){

                },
                beforeMount: function(){

                },
                mounted: function(){

                },
            }
            // 2 遍历页面中的对象
            for(let key in defaultObj){
                obj[key] ? this[key] = obj[key] : this[key] = defaultObj[key]
            }

            // 3 对象创建完毕 执行beforeCreate函数
            this.beforeCreate();

            // 4 挂载数据
            // 4.1 将传入的data属性赋给this
            if(obj.data){
                for(let key in this.data){
                    this[key] = obj.data[key];
                }
                this.$data = obj.data; // 设置全局变量
            }
            // 4.2 计算属性
            if(obj.computed){
                for(let key in obj.computed){
                    this[key] = obj.computed[key].call(this);
                }
            }

            // 5 执行created函数
            this.created();
            // 5.1 检查是否有el属性
            if(obj.el){
                this.el = $(obj.el);
                this.$el = $(obj.el); // 设置全局变量
            }
            // 5.2 检查是否有template属性
            if(this.template){
                this.template = obj.template;
                // 动态创建template里的所有的html元素 ...
            }

            // 6 执行beforeMounte函数
            this.beforeMount();

            // 7 用vue对象的数据属性替换模板中的内容
            // 7.1 替换data中的数据
            let html = this.el.innerHTML;
            for(let key in this.data){
                // 7.1.1 用属性替换属性名 {{}}一一对应
                html = html.replace(new RegExp("{{" + key + "}}","g"),this[key]);
            }
            // 7.2 替换computed中的数据
            for(let key in this.computed){
                html = html.replace(new RegExp("{{" + key + "}}","g"),this[key]);
            }
            // 7.3 将处理好的数据在虚拟节点DOM中准备,但也更新到页面
            this.el.innerHTML = html;

            // 8 执行mounted函数
            this.mounted();
			// ...
        }

        // 9 双向绑定
        addWatch(){
			// ...
        }
    }
    // 
    function $(str){
        if(str.charAt(0) == '#'){
            return document.getElementById(str.substring(1));
        }else if(str.charAt(0) == '.'){
            return document.getElementsByClassName(str.substring(1));
        }else{
            return document.getElementsByTagName(str);
        }
    }

上面写的是一个大概的框架思路,需要继续补充的可以去阅读以下vue中的源码(生命周期部分)

结语

关于vue的生命周期和钩子函数就写到这,如果想更深入理解最好的就是不断去了解各种函数所负责的业务场景,多写代码多思考就这样,如果文章有错可以指正,小弟承蒙各位大佬的意见

版权声明:本文为CSDN博主「@Umbrella」的原创文章,遵循CC 4.0
BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Umbrella_Um/article/details/100135410

发布了134 篇原创文章 · 获赞 80 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/Umbrella_Um/article/details/100135410
今日推荐