MVVM
MVVM是Model-View-ViewModel的缩写
- Model代表数据模型,可在Model中定义数据修改等操作的业务逻辑。
- View代表UI界面组件,负责将数据模型Model转化为UI进行展示。
- ViewModel表示监听模型中数据的改变并控制视图的行为,用于处理用户交互。简单来说,ViewModel是一个同步View和Model的对象,用来连接View和Model。
MVVM架构中View和Model之间并没有直接的联系,而是通过ViewModel进行交互。
Model和ViewModel之间的交互是双向的,因此View上数据的变化会同步到Model中,而Model数据的变化也会立即反映到View上。
ViewModel通过双向数据绑定将View层和Model层连接起来,而View和Model之间的同步工作完全是自动的,无需人为干预。开发者只需要关注业务逻辑,无需手动操作DOM,无需关注数据状态的同步问题,复杂的数据状态维护完全由MVVM来统一管理。
Vue双向数据绑定
Vue最显著的两个核心是数据驱动和组件系统
Vue实现数据双向绑定的原理是Object.defineProperty()
方法,即采用数据劫持并结合发布者-订阅者模式,通过Object.defineProperty()
方法来劫持属性的setter/getter
。当数据变动时发布消息给订阅者,触发对应监听回调。当将一个普通的JavaScript对象传递给Vue实例作为data
选项时,Vue会遍历其属性,并使用Object.defineProperty()
方法将他们转换为getter/setter
。用户是看不到setter/getter
方法,但在内部它们会让Vue追踪依赖,在属性被访问和修改时会通知其变化。
Vue的数据双向绑定会见MVVM作为数据绑定的入口,整合Observer、Compile、Watcher三者,通过Observer来监听自己Model数据的变化,通过Compile解析编译模板指令,最终利用Watcher搭建起Observer和Compile之间的通信桥梁,以达到数据变化视图更新,视图交互变化数据模型Model变更的双向绑定效果。
Vue生命周期
Vue实例都具有一个完整的生命周期,从开始创建、初始化数据、编译模板、挂载DOM、渲染更新渲染、卸载等一系列的过程。简单来说,就是Vue实例从创建到销毁的过程。
为什么会存在Vue的生命周期呢?Vue生命周期的作用是因为其生命周期内存在多个事件钩子,用以控制整个Vue实例的过程时更容易形成清晰的逻辑。Vue实例生命周期中,提供了一系列事件,使其在事件触发时注册JS方法,以使用自己注册的JS方法控制整个大局,在这些事件响应方法中this
直接指向的是Vue实例。
Vue生命周期可分为4大阶段8小阶段,分别是
- 创建前后
beforeCreate/created
- 载入前后
beforeMount/mounted
- 更新前后
beforeUpdate/updated
- 销毁前后
beforeDestroy/destroyed
当第一次页面加载时会触发beforeCreate
、created
、beforeMount
、mounted
钩子函数,DOM渲染则在mounted
中就已经完成了。
Vue实例生命周期钩子
每个Vue实例在被创建时都需经过一系列初始化过程,例如,设置数据监听、编译模板、将实例挂在到DOM并在数据变化时更新DOM等。同时在此过程中也会运行一些称为生命周期钩子的函数,以方便用户在不同阶段添加代码。
钩子 | 描述 |
---|---|
beforeCreate | 组件实例刚刚被创建,组件属性计算之前。 |
created | 用来在实例创建之后执行代码,组件实例创建完成,属性已绑定,DOOM未生成,$el 属性不存在。 |
beforeMount | 模板编译或挂载之前 |
mounted | 模板编译或挂载之后,此时并不能保证组件已在document中。 |
beforeUpdate | 组件更新之前 |
updated | 组件更新之后 |
beforeDestroy | 组件销毁前调用 |
destroyed | 组件销毁后调用 |
生命周期钩子是在Vue对象生命周期的某个阶段执行的已定义的方法,从初始化到被销毁,对象都会遵循不同的生命阶段。
<!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>vue</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="app">
<input type="text" v-model="input.msg" ref="msg" />
<button @click="onHandle">更新</button>
</div>
<script>
const vm = new Vue({
//DOM
el:"#app",
//数据
data(){
return {
input:{msg:"hello world"}
};
},
//生命周期钩子函数
beforeCreate(){
console.log(1, "before create", this.$el, this.input);
},
created(){
console.log(2, "created", this.$el, this.input);
},
beforeMount(){
this.$set(this.input, "msg", "helo");
console.log(3, "before mount", this.$el, this.input);
},
mounted(){
console.log(4, "mounted", this.$el, this.input, this.$refs.msg);
},
beforeUpdate(){
console.log(5, "before update", this.$el, this.input);
},
updated(){
console.log(6, "updated", this.$el, this.input);
},
beforeDestroy(){
console.log(7, "before destroy", this.$el, this.input);
},
destroyed(){
console.log(8, "destroyed", this.$el, this.input);
},
//
methods:{
onHandle(){
this.input.msg = "hi";
}
}
});
</script>
</body>
</html>
Vue内置方法属性和生命周期的运行顺序
运行顺序 | 属性方法 |
---|---|
1 | props |
2 | methods |
3 | data |
4 | computed |
5 | watch |
创建期间的生命周期函数
创建前beforeCreate
- 组件创建会执行生命周期函数
beforeCreate
,可在当前生命周期中创建一个Loading,当页面加载完成后再移除Loading。 -
beforeCreate
在当前生命周期函数中是访问不到其他生命周期函数以及data属性的。
Vue实例化后会创建一个Vue类的对象用来处理DOM元素,这个对象的生命周期可以通过beforeCreate
钩子函数来访问。
Vue实例刚从内存中被创建出来,此时还没有初始化好data
和methods
属性。
在Vue实例初始化之后,数据观测data observer
和event/watcher
事件配置之前被调用。
beforeCreate
实例创建前会遍历data
对象下所有属性并将其转化为setter/getter
,也就是为其添加一个被观察者,后续添加新属性时视图不用更新即可使用,后续添加的属性并不会放到观察者对象中,此时数据并没有和模板建立联系,因此不能操作该属性。如果要在实例挂在完成后使添加的属性触发视图的更新,可使用$set
方法,$set
方法会向被观察者对象中新增自定义的属性。
创建后created
- 当created生命周期函数执行时会将data和methods身上的属性和方法添加到vm实例上
- created执行时会遍历data的属性并为属性添加setter和getter方法
- 可以在created生命周期函数中发起AJAX数据请求
在created
阶段,对象及其事件已经完全初始化, created
是访问此阶段并编写代码的钩子,简单来说,此阶段是具有默认特性的对象。
Vue实例已经创建完成后被调用,在这一步Vue实例已经完成配置:数据观测(data observer)、属性和方法的运算(computed
)、watch/event
事件回调。然而,挂载阶段还没开始,$el
属性目前尚不可见。
在模板渲染成HTML前调用,即通常初始化某些属性值,然后再渲染成视图。
Vue实例已经在内存中创建完成,此时data
和methods
已经创建完成,此时还未开始编译模板。
created
阶段Vue实例已经被创建完毕,属性已经绑定,属性是可以操作的。但DOM还不存在,$el
属性也还不可以操作。此时也可以使用axios请求,但页面还未被渲染出来,若请求时间过长则会出现长时间的白屏的现象,因此推荐添加Loading界面。
挂载前 beforeMount
- 生命周期函数
beforeMount
可以对data
中的数据做最后修改 - 在
beforeMount
生命周期函数中添加的属性没有setter
和getter
方法,若需要则需使用$set
方法。 -
beforeMount
生命周期函数中模板和数据未进行结合
beforeMount
钩子调用阶段会检查是否具有模板用于要在DOM中呈现的对象,若没有模板则将定义元素的外部HTML视为模板。
beforeMount
在挂载开始之前被调用,相关的render
函数首次被调用。beforeMount
阶段已经完成了模板的编译但还未挂在到页面中。
挂载后mounted
- mounted挂载后数据和模板会进行结合并生成真正的DOM结构
- mounted函数中可访问到真实的DOM结构
- mounted函数中可用于插件实例化
一旦模板准备就绪,就会将数据放入模板并创建可呈现元素。使用新数据填充元素替换DOM元素。mounted
钩子表示DOM已准备就绪并放置到页面中。
el
被新创建的vm.$el
替换,并挂载到实例上去之后调用该钩子。
在模板渲染成HTML后调用,通常是初始化页面完成后,再对HTML的DOM节点进行一些需要的操作。
mounted
阶段此时已经将编译好的模板挂载到了页面指定的容器中显示。
运行期间的生命周期函数
更新前beforeUpdate
- beforeUpdate会多次执行,当数据更新前会执行。
- beforeUpdate更新前数据还未和模板结合,可在此函数中做更新数据的最后修改。
当外部事件或用户输入时,beforeUpdate
钩子在反映原始DOM元素的更改之前被触发,此阶段表示更改已经完成,但尚未准备好更新DOM。
数据更新时调用,发生在虚拟DOM重新渲染和打补丁之前。可以在狗子中进一步地更改状态,这不会出发附加的重渲染过程。
状态更新之前执行beforeUpdate
钩子函数,此时data
中的状态值是最新的,但界面上显示的数据还是旧的,因为此时还没有开始重新渲染DOM节点。
更新后updated
- updated函数是更新的数据和模板进行组合的位置
- 可在updated函数中获取到数据更新后最新的DOM结构
- 可在updated函数中做插件的实例化,但需添加判断条件否则会非常消耗性能。
updated
钩子表示在DOM中程序的更改,通过实际更新DOM对象并触发updated
方法,屏幕上的变化才得以呈现。
由于数据更改导致的虚拟DOM重新渲染和打补丁,在这之后会调用该钩子。
当这个钩子被调用时,组件DOM已经更新,所以现在可以执行依赖于DOM的操作。然而大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。
updated
函数在Vue实例更新完毕后调用,此时data
中的状态值和界面上显示的数据都已经完成了更新,界面已经被重新渲染好了。
销毁期间的生命周期函数
销毁前beforeDestroy
- beforeDestroy函数中可访问到真实的DOM结构,可在当前生命周期函数中做事件的解绑、监听移除等操作。
当Vue对象被破坏并从内存中释放之前beforeDestroy
钩子被触发
Vue实例销毁之前调用,在这一步中Vue实例仍然完全可用。
beforeDestroy
钩子函数在Vue实力销毁之前调用,此时Vue实力仍然完全可用。
销毁后destroyed
- destroyed函数执行时会将vm和模板之间的关联断开
- destroyed函数中无法通过
ref
访问到真实DOM结构
destroyed
钩子被成功运行销毁对象上调用,对象停止并从内存中删除。
Vue实例销毁后调用,调用后Vue实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不会被调用。
destroyed
钩子函数在Vue实例销毁后调用,调用后Vue实例指向会解除绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。
created 与 mounted
-
beforeCreated
时el
和data
并未初始化 -
created
阶段完成data
数据的初始化,此时el
还不存在。 -
beforeMount
阶段完成了el
和data
的初始化 -
mounted
阶段完成挂载。
created
在模板渲染成HTML前调用,即通常初始化某些属性值,然后再渲染成视图。
mounted
在模板渲染成HTML后调用,通常是初始化页面完成后再对HTML的DOM节点进行一些需要的操作。
通常created
使用的次数多,而mounted
通常是在一些插件的使用或组件的使用中进行操作。