詳細(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ū)動(dòng)視圖,從而避免了操作DOM。
MVVM的基本思想是將模型數(shù)據(jù)和用戶視圖解耦(Decoupling),即Model和View分離開來,使得它們之間的依賴關(guān)系降到最小。然后通過ViewModel來協(xié)調(diào)它們之間的通信。當(dāng)Model發(fā)生變化時(shí),我們不需要手動(dòng)更新View,而是可以通過Vue的響應(yīng)式機(jī)制,讓Vue自動(dòng)更新View。同樣,當(dāng)用戶與View交互時(shí),我們也不需要手動(dòng)修改Model,而是可以通過Vue的指令和事件機(jī)制,讓Vue自動(dòng)更新Model。這種解耦機(jī)制使得我們可以更加專注于業(yè)務(wù)邏輯的實(shí)現(xiàn),而不需要過多地關(guān)注View和Model之間的細(xì)節(jié)。
- Model:Vue應(yīng)用程序中的數(shù)據(jù)模型,通常是一個(gè)JavaScript對(duì)象或數(shù)組。這些數(shù)據(jù)模型被存儲(chǔ)在Vue實(shí)例的data屬性中。
- View:Vue應(yīng)用程序中的視圖界面,通常是由HTML模板和Vue指令組成的。Vue的模板語法允許開發(fā)人員在HTML中綁定數(shù)據(jù)和表達(dá)式,以實(shí)現(xiàn)動(dòng)態(tài)更新。
- ViewModel:ViewModel是Vue應(yīng)用程序的核心,它是一個(gè)Vue實(shí)例,它充當(dāng)Model和View之間的橋梁。ViewModel負(fù)責(zé)管理數(shù)據(jù)和行為,它可以將Model數(shù)據(jù)綁定到View上,同時(shí)也可以響應(yīng)View上的事件和用戶交互。ViewModel中包含了一個(gè)Watcher和Directive,它們可以監(jiān)聽Model數(shù)據(jù)的變化,并自動(dòng)更新View。
2. Vue響應(yīng)式
Vue的響應(yīng)式機(jī)制是指當(dāng)Vue實(shí)例中的數(shù)據(jù)發(fā)生變化時(shí),與之關(guān)聯(lián)的視圖會(huì)自動(dòng)更新。實(shí)現(xiàn)Vue響應(yīng)式機(jī)制需要借助ES5的Object.defineProperty()
方法,使用該方法監(jiān)聽對(duì)象屬性的變化。
Object.defineProperty(obj, prop, descriptor)
用來定義對(duì)象的屬性。函數(shù)中傳入三個(gè)參數(shù),obj
表示要定義屬性的對(duì)象,prop
表示要定義的屬性名,descriptor
是一個(gè)包含屬性描述符的對(duì)象。下面這段代碼是該函數(shù)的基本使用:
let data = {}; let name = '小明'; Object.defineProperty(data, "name", { get: function(){ console.log('get') return name }, set: function(newValue){ console.log('set') name = newValue } }) // 測(cè)試 console.log(data.name) data.name = '小紅' console.log(data.name)
3. Vue監(jiān)聽對(duì)象
3.1 監(jiān)聽普通對(duì)象
以下代碼模擬了的監(jiān)聽對(duì)象的過程,當(dāng)data對(duì)象中的數(shù)據(jù)發(fā)生變化時(shí),就會(huì)調(diào)用updateView()
函數(shù),觸發(fā)視圖更新。
// 觸發(fā)視圖更新,模擬Model變化后,View發(fā)生改變,簡(jiǎn)單起見就直接打印一個(gè)結(jié)果 function updateView(){ console.log('視圖更新') } // 重新定義屬性,實(shí)現(xiàn)對(duì)數(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)聽對(duì)象屬性 function observer(target){ // 如果監(jiān)聽的數(shù)據(jù)類型不是對(duì)象,直接返回原數(shù)據(jù) if(typeof target !== 'object' || target === null){ return target } // 重新定義對(duì)象的每個(gè)屬性 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) // 測(cè)試 data.name = '小紅' data.age = 21 data.name // 會(huì)觸發(fā)getter函數(shù) data.age
3.2 監(jiān)聽復(fù)雜對(duì)象(深度監(jiān)聽)
假如數(shù)據(jù)對(duì)象中還包含對(duì)象,此時(shí)就需要進(jìn)行深度監(jiān)聽,例如data中的info
數(shù)據(jù)。只需要在上述的defineReactive()
函數(shù)中,再次調(diào)用observer()
函數(shù),對(duì)傳入的對(duì)象的屬性值value
進(jìn)行監(jiān)聽。
設(shè)置新值時(shí),如果設(shè)置的值還是一個(gè)對(duì)象類型,此時(shí)還需要對(duì)新值進(jìn)行監(jiān)聽。
function updateView(){ console.log('視圖更新') } // 重新定義屬性,實(shí)現(xiàn)對(duì)數(shù)據(jù)的監(jiān)聽 function defineReactive(target, key, value){ // 再次對(duì)傳入的對(duì)象的屬性值調(diào)用監(jiān)聽函數(shù) observer(value) // 核心 API Object.defineProperty(target, key, { get(){ return value }, set(newValue){ // 對(duì)新設(shè)置的值進(jìn)行監(jiān)聽 observer(newValue) if(newValue !== value){ value = newValue updateView() } } }) } // 監(jiān)聽對(duì)象屬性 function observer(target){ // 如果監(jiān)聽的數(shù)據(jù)類型不是對(duì)象,直接返回原數(shù)據(jù) if(typeof target !== 'object' || target === null){ return target } // 重新定義對(duì)象的每個(gè)屬性 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) // 測(cè)試 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)建新對(duì)象,原型指向 oldArrayProperty ,再擴(kuò)展新的方法不會(huì)影響原型 const arrProto = Object.create(oldArrayProperty); // 對(duì)數(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 時(shí)也是會(huì)獲取最新的值 value = newValue // 觸發(fā)更新視圖 updateView() } } }) } // 監(jiān)聽對(duì)象屬性 function observer(target) { if (typeof target !== 'object' || target === null) { // 不是對(duì)象或數(shù)組 return target } // 修改數(shù)組的原型 if (Array.isArray(target)) { target.__proto__ = arrProto } // 重新定義各個(gè)屬性(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) // 測(cè)試 data.nums.push(4) // 監(jiān)聽數(shù)組
5. 使用 Object.defineProperty 監(jiān)聽數(shù)據(jù)的缺點(diǎn)
- 深度監(jiān)聽需要對(duì)數(shù)據(jù)對(duì)象遞歸到底,一次性計(jì)算量大。
- 如果數(shù)據(jù)新增屬性和刪除屬性,則無法監(jiān)聽到,需要使用
Vue.set()
和Vue.delete()
方法。 - 無法原生監(jiān)聽數(shù)組,需要特殊處理。
到此這篇關(guān)于詳細(xì)聊聊Vue中的MVVM模式原理的文章就介紹到這了,更多相關(guān)Vue中MVVM內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue前端實(shí)現(xiàn)表格數(shù)據(jù)增查改刪功能
增刪改查是我們寫項(xiàng)目百分之七十會(huì)遇到的代碼,下面這篇文章主要給大家介紹了關(guān)于vue前端實(shí)現(xiàn)表格數(shù)據(jù)增查改刪功能的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2024-05-05vue3.0+vue-router+element-plus初實(shí)踐
這篇文章主要介紹了vue3.0+vue-router+element-plus初實(shí)踐,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12詳解vue前后臺(tái)數(shù)據(jù)交互vue-resource文檔
本篇文章主要介紹了vue前后臺(tái)數(shù)據(jù)交互vue-resource文檔,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07Vue3.0實(shí)現(xiàn)圖片預(yù)覽組件(媒體查看器)功能
最近項(xiàng)目中有個(gè)場(chǎng)景,一組圖片、視頻、音頻、文件數(shù)據(jù),要求點(diǎn)擊圖片可以放大預(yù)覽,左右可以切換音視頻、文件,支持鼠標(biāo)及各種鍵控制?縮放,左右旋轉(zhuǎn),移動(dòng)等功能,這篇文章主要介紹了Vue3.0實(shí)現(xiàn)圖片預(yù)覽組件(媒體查看器),需要的朋友可以參考下2023-12-12vue2中的keep-alive使用總結(jié)及注意事項(xiàng)
vue2.0提供了一個(gè)keep-alive組件用來緩存組件,避免多次加載相應(yīng)的組件,減少性能消耗。本文給大家介紹vue2中的keep-alive使用總結(jié)及注意事項(xiàng),需要的朋友參考下吧2017-12-12