詳細(xì)聊聊Vue中的MVVM模式原理
1. MVVM模式
傳統(tǒng)的組件是靜態(tài)渲染,數(shù)據(jù)更新需要操作DOM。Vue框架采用了MVVM(Model-View-ViewModel)模式來管理應(yīng)用程序的數(shù)據(jù)模型(Model)和視圖界面(View)的交互,即數(shù)據(jù)驅(qū)動視圖,從而避免了操作DOM。
MVVM的基本思想是將模型數(shù)據(jù)和用戶視圖解耦(Decoupling),即Model和View分離開來,使得它們之間的依賴關(guān)系降到最小。然后通過ViewModel來協(xié)調(diào)它們之間的通信。當(dāng)Model發(fā)生變化時,我們不需要手動更新View,而是可以通過Vue的響應(yīng)式機(jī)制,讓Vue自動更新View。同樣,當(dāng)用戶與View交互時,我們也不需要手動修改Model,而是可以通過Vue的指令和事件機(jī)制,讓Vue自動更新Model。這種解耦機(jī)制使得我們可以更加專注于業(yè)務(wù)邏輯的實(shí)現(xiàn),而不需要過多地關(guān)注View和Model之間的細(xì)節(jié)。

- Model:Vue應(yīng)用程序中的數(shù)據(jù)模型,通常是一個JavaScript對象或數(shù)組。這些數(shù)據(jù)模型被存儲在Vue實(shí)例的data屬性中。
- View:Vue應(yīng)用程序中的視圖界面,通常是由HTML模板和Vue指令組成的。Vue的模板語法允許開發(fā)人員在HTML中綁定數(shù)據(jù)和表達(dá)式,以實(shí)現(xiàn)動態(tài)更新。
- ViewModel:ViewModel是Vue應(yīng)用程序的核心,它是一個Vue實(shí)例,它充當(dāng)Model和View之間的橋梁。ViewModel負(fù)責(zé)管理數(shù)據(jù)和行為,它可以將Model數(shù)據(jù)綁定到View上,同時也可以響應(yīng)View上的事件和用戶交互。ViewModel中包含了一個Watcher和Directive,它們可以監(jiān)聽Model數(shù)據(jù)的變化,并自動更新View。
2. Vue響應(yīng)式
Vue的響應(yīng)式機(jī)制是指當(dāng)Vue實(shí)例中的數(shù)據(jù)發(fā)生變化時,與之關(guān)聯(lián)的視圖會自動更新。實(shí)現(xiàn)Vue響應(yīng)式機(jī)制需要借助ES5的Object.defineProperty()方法,使用該方法監(jiān)聽對象屬性的變化。
Object.defineProperty(obj, prop, descriptor)用來定義對象的屬性。函數(shù)中傳入三個參數(shù),obj表示要定義屬性的對象,prop表示要定義的屬性名,descriptor是一個包含屬性描述符的對象。下面這段代碼是該函數(shù)的基本使用:
let data = {};
let name = '小明';
Object.defineProperty(data, "name", {
get: function(){
console.log('get')
return name
},
set: function(newValue){
console.log('set')
name = newValue
}
})
// 測試
console.log(data.name)
data.name = '小紅'
console.log(data.name)

3. Vue監(jiān)聽對象
3.1 監(jiān)聽普通對象
以下代碼模擬了的監(jiān)聽對象的過程,當(dāng)data對象中的數(shù)據(jù)發(fā)生變化時,就會調(diào)用updateView()函數(shù),觸發(fā)視圖更新。
// 觸發(fā)視圖更新,模擬Model變化后,View發(fā)生改變,簡單起見就直接打印一個結(jié)果
function updateView(){
console.log('視圖更新')
}
// 重新定義屬性,實(shí)現(xiàn)對數(shù)據(jù)的監(jiān)聽
function defineReactive(target, key, value){
// 核心 API
Object.defineProperty(target, key, {
get(){
return value
},
set(newValue){
if(newValue !== value){
value = newValue
updateView()
}
}
})
}
// 監(jiān)聽對象屬性
function observer(target){
// 如果監(jiān)聽的數(shù)據(jù)類型不是對象,直接返回原數(shù)據(jù)
if(typeof target !== 'object' || target === null){
return target
}
// 重新定義對象的每個屬性
for(let key in target){
defineReactive(target, key, target[key])
}
}
// 數(shù)據(jù)
let data = {
name: '小明',
age: 20,
info: {
address: '北京'
}
}
// 監(jiān)聽數(shù)據(jù)
observer(data)
// 測試
data.name = '小紅'
data.age = 21
data.name // 會觸發(fā)getter函數(shù)
data.age

3.2 監(jiān)聽復(fù)雜對象(深度監(jiān)聽)
假如數(shù)據(jù)對象中還包含對象,此時就需要進(jìn)行深度監(jiān)聽,例如data中的info數(shù)據(jù)。只需要在上述的defineReactive()函數(shù)中,再次調(diào)用observer()函數(shù),對傳入的對象的屬性值value進(jìn)行監(jiān)聽。
設(shè)置新值時,如果設(shè)置的值還是一個對象類型,此時還需要對新值進(jìn)行監(jiān)聽。
function updateView(){
console.log('視圖更新')
}
// 重新定義屬性,實(shí)現(xiàn)對數(shù)據(jù)的監(jiān)聽
function defineReactive(target, key, value){
// 再次對傳入的對象的屬性值調(diào)用監(jiān)聽函數(shù)
observer(value)
// 核心 API
Object.defineProperty(target, key, {
get(){
return value
},
set(newValue){
// 對新設(shè)置的值進(jìn)行監(jiān)聽
observer(newValue)
if(newValue !== value){
value = newValue
updateView()
}
}
})
}
// 監(jiān)聽對象屬性
function observer(target){
// 如果監(jiān)聽的數(shù)據(jù)類型不是對象,直接返回原數(shù)據(jù)
if(typeof target !== 'object' || target === null){
return target
}
// 重新定義對象的每個屬性
for(let key in target){
defineReactive(target, key, target[key])
}
}
// 數(shù)據(jù)
let data = {
name: '小明',
age: 20,
info: {
address: '北京'
}
}
// 監(jiān)聽數(shù)據(jù)
observer(data)
// 測試
data.name = '小紅'
data.age = {num: 21}
data.age.num = 22
data.info.address = '上海'

4. Vue監(jiān)聽數(shù)組
// 觸發(fā)更新視圖
function updateView() {
console.log('視圖更新')
}
// // 重新定義數(shù)組原型
const oldArrayProperty = Array.prototype
// 創(chuàng)建新對象,原型指向 oldArrayProperty ,再擴(kuò)展新的方法不會影響原型
const arrProto = Object.create(oldArrayProperty);
// 對數(shù)常用的方法進(jìn)行擴(kuò)展
['push', 'pop', 'shift', 'unshift', 'splice'].forEach(methodName => {
arrProto[methodName] = function () {
oldArrayProperty[methodName].call(this, ...arguments)
updateView() // 觸發(fā)視圖更新
Array.prototype[methodName].call(this, ...arguments)
}
})
// 重新定義屬性,監(jiān)聽起來
function defineReactive(target, key, value) {
// 深度監(jiān)聽
observer(value)
// 核心 API
Object.defineProperty(target, key, {
get() {
return value
},
set(newValue) {
if (newValue !== value) {
// // 深度監(jiān)聽
observer(newValue)
// 設(shè)置新值
// 注意,value 一直在閉包中,此處設(shè)置完之后,再 get 時也是會獲取最新的值
value = newValue
// 觸發(fā)更新視圖
updateView()
}
}
})
}
// 監(jiān)聽對象屬性
function observer(target) {
if (typeof target !== 'object' || target === null) {
// 不是對象或數(shù)組
return target
}
// 修改數(shù)組的原型
if (Array.isArray(target)) {
target.__proto__ = arrProto
}
// 重新定義各個屬性(for in 也可以遍歷數(shù)組)
for (let key in target) {
defineReactive(target, key, target[key])
}
}
// 準(zhǔn)備數(shù)據(jù)
const data = {
// name: '小明',
// age: 20,
// info: {
// address: '北京' // 需要深度監(jiān)聽
// },
nums: [10, 20, 30]
}
// 監(jiān)聽數(shù)據(jù)
observer(data)
// 測試
data.nums.push(4) // 監(jiān)聽數(shù)組5. 使用 Object.defineProperty 監(jiān)聽數(shù)據(jù)的缺點(diǎn)
- 深度監(jiān)聽需要對數(shù)據(jù)對象遞歸到底,一次性計算量大。
- 如果數(shù)據(jù)新增屬性和刪除屬性,則無法監(jiān)聽到,需要使用
Vue.set()和Vue.delete()方法。 - 無法原生監(jiān)聽數(shù)組,需要特殊處理。
到此這篇關(guān)于詳細(xì)聊聊Vue中的MVVM模式原理的文章就介紹到這了,更多相關(guān)Vue中MVVM內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vite打包優(yōu)化CDN壓縮的分析實(shí)現(xiàn)
我們在日常的工作中肯定會遇到項(xiàng)目打包優(yōu)化等問題,本文主要介紹了vite打包優(yōu)化CDN壓縮的分析實(shí)現(xiàn),具有一定的參加價值,感興趣的可以了解一下2024-07-07
Vue 3自定義指令開發(fā)的相關(guān)總結(jié)
這篇文章主要介紹了Vue 3自定義指令開發(fā)的相關(guān)總結(jié),幫助大家更好的理解和使用vue框架,感興趣的朋友可以了解下2021-01-01

