Vue 实例

发布于 大漠

在刚接触Vue的时候,就知道 实例 在Vue中是一个重要的概念,在学习之后也整理了一篇有关于Vue实例和生命周期的学习笔记。经过一段时间的学习之后,重新再温习了一遍有关于Vue的实例,整理一下,提供给有需要的同学作为参考资料。

Vue 的基本原理

Vue是一个优势的JavaScript框架,其主要是用来处理 视图层(我的理解就是UI层)。当用户操作 View Model (即JavaScript,在Vue中指的是Vue实例,一个观察者)使其依照一定的逻辑取得需要改变的数据(Model),配合在HTML中Vue提供的模板语法来改变配置,重新渲染后使画面产生相应的变化(View)。用一张图来描述这个过程:

比如我们在浏览器中渲染出来的页面有个区域和“删除”按钮,当用户点击“删除”按钮之后,会移除这个区域。用户交互之后反馈给用户的是,界面(视图)中的区域被移除。看上去非常的简单,其实在Vue中,主要经历了以下几个过程:

  • (1) 用户在界面中点击“Remove”按钮进行交互
  • (2) 通过模板中的绑定触发Vue实例中注册的remove()事件
  • (3) remove()事件通过Ajax向服务器请求数据
  • (4) 服务器取得的数据后重新传给Vue实例
  • (5) Vue实例修改绑定的ViewModel
  • (6) ViewModel改变后触模板重新渲染
  • (7) 改变后的视图反馈给用户(用户看到重新渲染后的视图)

咱们可以用下图来描述这个交互的整个过程:

事实上Vue的原理要比这复杂的多,只不过用上图来阐述对于初学者而言在概念上有一定的辅助作用。但其也遵循一个原则:视图的状态由数据描述,并且通过数据来驱动视图变化

深入的概念还无法理解的情况之下,我们暂时只需要先记住,在Vue中视图和数据之间的关系是一个非常简单的关系,即双向数据绑定

Vue采用发布者 —— 订阅者模式实现双向数据绑定,首先Vue将会获得需要监听的对象的所有属性,通过Object.defineProperty方法完成对象属性的劫持,将其转化为gettersetter,当属性被访问或修改时,立即将变化能知给订阅者,并由订阅者完成相应的逻辑操作。

我们可以用下图来描述双向数据绑定主要流程:

  • Observer:主要处理属性监听逻辑,将监听属性转化为getset属性,当属性被访问时,调用dep.depend()方法,而属性被修改时,则调用了dep.notify()方法
  • Dep:担任发布者的角色,维护访问者列表,负责订阅者的添加和通知工作,上面所提到的depend()notify()方法在这里实现
  • Watcher:担任订阅者角色,即Dep.target,可以订阅多个Dep,在每次收到发布者消息通知时触发update()方法执行更新逻辑

创建Vue实例

接下来,我们来创建一个Vue实例,就是前面提到的**ViewModel**部分。Vue实例是Vue中很重要的一部分,创建他很简单:

let app = new Vue({
    
})

创建Vue实例很简单,通过new Vue({})即可。事实上,Vue实例就是一个JavaScript对象,而且常常将这个对象赋值给一个app变量。

Vue的第一个参数是options,它会传给Vue实例这个对象。options这个参数主要包括:

  • el:Vue实例需要挂载的一个DOM元素,一般是index.html文件中的div#app元素
  • data:需要一个输出取得结果的数据
  • methods:方法
  • Vue实例生命周期的钩子函数

比如下面这个示例:

let app = new Vue({
    el: '#app',
    data () {
        return {
            message: 'W3cplus.com'
        }
    },
    methods: {
        getRemoteMessage () {
            Promise.resolve('获取远程数据:大漠')
                .then((res) => {
                    this.message = res
                })
        }
    }
})

除了上面这种方式,Vue实例的挂载还有另外一种方式:

// main.js
new Vue({
    render: h => h(App),
}).$mount('#app')

而给Vue实例传递参数,可以像下面这样的方式:

<!-- App.vue -->
<script>
    export default {
        name: 'app',
        data () {
            return {
                message: 'W3cplus.com'
            }
        },
        methods: {
            getRemoteMessage () {
                Promise.resolve('获取远程数据:大漠')
                    .then((res) => {
                        this.message = res
                    })
            }
        }
    }
</script>

上面的示例主要做了三件事情:

  • 将Vue实例挂载在页面中idapp的一个div元素上
  • data中初始化了一个message,并且在<template>中以{{message}}方式将data中的message显示在页面上
  • 定义了一个getRemoteMessage()方法,该方法会以非同步的方式取得message
  • <template>中的一个button绑定了定义好的getRemoteMessage()方法
  • 当用户点击界面中的按钮,将会获取远程的一个数据,并且更改<template>message的值

效果如下:

创建一个Vue实例就是这么的简单。

最开始我们也提到了,Vue实例是Vue中重要的部分之一,甚至可以说,任何一个Vue的应用程序都是从Vue实例开始的,大创建实例后,可以通过操作实例中的参数来改变页面的配置

以上示例代码可以查阅app-vue-instance项目的step1分支。

Vue实例的生命周期

Vue实例中还有另一个重要的内容,那就是Vue实例的生命周期,在Vue实例中会在各个生命周期中提供相应的钩子函数(Hooks事件),这些钩子函数让开发者可以在Vue实例阶段做相应的处理。Vue官方为Vue实例的生命周期提供了一张非常详细的图:

上图展示了Vue实例整个生命周期,其中红框白底就是生命周期中具备的钩子函数:

  • beforeCreate:Vue实例初始化的时候会立即调用,其发生在未创立Vue实例之前,这个时候在Vue实例中的设置都还未配置完成,比如data
  • created:Vue实例创建完成,此时Vue实例中的配置除了$el外全部配置,而$el只有在Vue实例挂载到相应的HTML元素时才会生效
  • beforeMount:在Vue实例挂载到目标元素之前被调用,这时的$el会是还未被Vue实例中的定义渲染的初始设定的模板
  • mounted:Vue实例上的设置已经安装上模板,这里的$el是已经由实例中的定义渲染成真成的页面
  • beforeUpdate:当Vue实例中的data发生变化后或是执行vm.$forceUpdate()调用,这时的页面还未被重新沉浸而改变页面
  • updated:在重新渲染页面后被调用,这时的页面已经被重新渲染成改变后的页面
  • beforeDestroy:Vue实例被销毁前调用,这个时候Vue实例还是拥有完整的功能
  • destroyed:Vue实例被销毁,这个时候Vue实例中的任何定义都已被解除绑定,此时对Vue实例做的任何操作都会失效

接下来看一个小示例,把分支切换到step2,来看看Vue实例中操作各个钩子函数,然后打印出data$el,看看在各个钩子函数中它们是如何变化的。

<!-- App.vue -->
<template>
    <div id="app">
        <img alt="Vue logo" src="./assets/logo.png">
        <h2>{{ message }}</h2>
        <div>
            <button @click="getRemoteMessage">获取远程数据</button>
        </div>
    </div>
</template>

<script>
    export default {
        name: 'app',
        data () {
            return {
                message: 'W3cplus.com'
            }
        },
        methods: {
            getRemoteMessage () {
                Promise.resolve('获取远程数据:大漠')
                    .then((res) => {
                        this.message = res
                    })
            }
        }
    }
</script>

根据上图,将Vue实例生命周期中的钩子函数分成四组:

  • beforeCreatecreated:创建Vue实例
  • beforeMountmounted:挂载目标元素
  • beforeUpdateupdated:改变后重新渲染
  • beforeDestroydestroyed:销毁Vue实例

beforeCreatecreated

先来看beforeCreatecreated两个钩子函数,在Vue实例中添加这两个钩子函数,打印出相应的data$el

beforeCreate () {
    console.log(`>>> ==== 钩子函数 beforeCreate ==== >>>`)
    console.log(`this.message: ${this.message}`)
    console.log(`this.$el: ${this.$el}`)
    console.log(`>>> ==== End ==== >>>`)
},
created () {
    console.log(`>>> ==== 钩子函数 created ==== >>>`)
    console.log(`this.message: ${this.message}`)
    console.log(`this.$el: ${this.$el}`)
    console.log(`>>> ==== End ==== >>>`)
}

结果如下:

由于beforeCreate阶段,Vue实例还没有创建,所以message$el都是undefined;而到了created阶段时,Vue实例已经创建了,所以message变成了W3cplus.com,但$el因为还没有挂载到目标元素,所以依旧是unddefined

也就是说,beforeCreate钩子中是不能对Vue实例中的任何东西做操作

beforeMountmounted

和前面的方式一样,在Vue实例中添加钩子函数beforeMountmounted

beforeMount () {
    console.log(`>>> |_==_| 钩子函数 beforeMount |_==_| >>>`)
    console.log(`this.message: ${this.message}`)
    console.log(`this.$el: ${this.$el}`)
    console.log(`>> |_==_| this.$el.outerHTML 开始 |_==_| >>`)
    console.log(this.$el.outerHTML)
    console.log(`>> |_==_| this.$el.outerHTML 结束 |_==_|`)
    console.log(`>>> |_==_| End |_==_| >>>`)
},
mounted () {
    console.log(`>>> |_==_| 钩子函数 mounted |_==_| >>>`)
    console.log(`this.message: ${this.message}`)
    console.log(`this.$el: ${this.$el}`)
    console.log(`>> |_==_| this.$el.outerHTML 开始 |_==_| >>`)
    console.log(this.$el.outerHTML)
    console.log(`>> |_==_| this.$el.outerHTML 结束 |_==_| >>`)
    console.log(`>>> |_==_| End |_==_| >>>`)
}

结果如下:

在Vue实例的流程图中提到,开始执行beforeMount钩子函数时,此时$el还没有生成HTML到页面上,因此在执行this.$el.outerHTML时会报警,如上图所示。而在执行mounted钩子函数时,Vue实例已经绑定到元素上,所以这里看到的是渲染后的结果。

也就是说,beforeMount前同样不能操作DOM元素

beforeUpdateupdated

同样的,把beforeUpdateupdated钩子函数加入到Vue实例中:

beforeUpdate () {
    console.log(`>>> (^_^) 钩子函数 beforeUpdate (^_^) >>>`)
    console.log(`this.message: ${this.message}`)
    console.log(`this.$el: ${this.$el}`)
    console.log(this.$el.outerHTML)
    console.log(`>>> (^_^) End (^_^) >>>`)
},
updated () {
    console.log(`>>> (^_^) 钩子函数 updated (^_^) >>>`)
    console.log(`this.message: ${this.message}`)
    console.log(`this.$el: ${this.$el}`)
    console.log(this.$el.outerHTML)
    console.log(`>>> (^_^) End (^_^) >>>`)
}

当你点击页面上的按钮时,打印出来的结果如下:

beforeDestroydestroyed

beforeDestroy () {
    console.log(`(^_^) 钩子函数 beforeDestroy (^_^)`)
    console.log(`this.message: ${this.message}`)
    console.log(`this.$el:${this.$el}`)
    console.log(this.$el.outerHTML)
    console.log(`(^_^) End (^_^)`)
},
destroyed () {
    console.log(`(^_^) 钩子函数 destroyed (^_^)`)
    console.log(`this.message: ${this.message}`)
    console.log(`this.$el:${this.$el}`)
    console.log(this.$el.outerHTML)
    console.log(`(^_^) End (^_^)`)
}

第一次执行app.__vue__.$destroy()会打印出相应的信息,再执行一次将会报错,如下图所示:

正如上面所示,执行beforeDestroy后,即将执行销毁Vue实例,如果想要释放一些资源,可以在这里操作;当执行destroyed时,Vue实例将会销毁。比如上面的示例,执行app.__vue__.$destroy()后,再点击页面上的按钮,不会有任何的反应,这是因为我们的Vue实例已销毁。

小结

本文从Vue的基本原理着手,学习了Vue的一些基本原理以及相关的概念,然后学习Vue实例的创建以及Vue实例生命周期中的钩子函数,并通过简单的实例,演示了Vue实例中不同钩子函数时实例的状态。这样对于学习Vue实例以及生命周期就不仅仅是停在概念上的,而是知道每个不同周期中对应钩子函数中具体的行为。这样一来,在实际使用Vue的时候,更能清楚的什么事情该在什么样的钩子函数中执行,才能起到相应的效果。特别是对于DOM的操作,掌握这些尤其重要。Air Jordan II High