問題:
1、MVVM 的定義

M (Model): 數據來源,服務器上業務邏輯操作
V (View): 界面,頁面
VM (ViewModel): view 和 model 的核心樞紐,如 vue.js
Model 和 ViewModel 的雙向關系:
1、 Model 通過 Ajax (服務器) 通信,發送數據給 ViewModel
2、 ViewModel 通過 Ajax (服務器) 通信,回傳數據給 Model
View 和 ViewModel 的雙向關系
1、 ViewModel 的數據改變,會映射到 View(即可以即時改變 View 顯示的內容)
2、View 的內容改變,也會同時改變 ViewModel 對應的數據
MVVM模式常見於用於構建用戶界面的客戶端應用
- 微軟的WPF,構建客戶端應用的
- 手機應用,iOS APP, Android APP
- Web應用
MVVM的設計理念是前端工程化的體現;讓展示層展示邏輯,盡可能的交於數據來驅動;使前端工程師從繁瑣的DOM操作中解脫處理;使數據的變化直接體現給用戶,而不是將數據的變化交於前端工程師后再進行DOM操作展示數據變化
前端MVVM的設計理念是受到后端MVC理念的啟發的;在MVC的設計理念中,前端(V)是其中的一個環節,瀏覽器只負責現有頁面的渲染,不關心也無權操作數據
而MVVM 的出現,徹底將前端工程從后台分離出來,使數據的變化處理徹底交由前端操作,而后台只負責數據的處理,不再關心數據變化而帶來的展示層的變化
- 前端開發早期的時候都是操作DOM
- 后來使用jQuery讓我們提高了操作DOM的效率,但從開發的角度還是在大量的手動操作DOM
- MVVM模式讓以往手動操作DOM的方式徹底解脫了,它不要用戶自己操作DOM,而是將普通數據綁定到ViewModel上,會自動將數據渲染到頁面中,視圖變化會通知ViewModel層更新數據,數據變化也會通過ViewModel層更新數據,因此MVVM中最重要的角色就是ViewModel,真正意義上把視圖和數據實現了解耦,提高了開發效率
核心:
- MVVM模式讓我們從繁瑣的DOM操作中徹底解放了
- MVVM也叫數據驅動視圖
2、對比MVC 和 MVVM
認識MVC: Controller 負責 將 Model 的數據在 View 中顯示出來(即 Controller 負責將 Model 的數據賦值給View),比如在controller中寫document.getElementById("box").innerHTML = data[”title”],只是還沒有刻意建一個Model類出來而已。
M (Model): 模型,應用程序處理數據的部分,通常指從數據庫讀取數據
V (View): 視圖,界面, 應用程序處理界面顯示的部分,通常根據模型數據創建
C (Controller): 控制器, 應用程序控制用戶交互的部分,通常負責從View讀取數據,控制用戶輸入,向 Model 發送數據
斯坦福大學公開課上的這幅圖來說明,這可以說是最經典和最規范的MVC標准

從上圖可以看出: C可以直接引用 V 和 M, V 和 M 不能(不應該)直接引用 C
View 和 Controller 之間的交互:
View 把事件傳遞給 Controller,Controller 相當於靶子,View 和 Controller 有兩種交互方式:一種是: target->action,另一種是:協議->委托,委托有兩種方式 代理和數據源:代理是 should,will,did 等等的委托,數據源是 data,count 等等的委托
Model 和 Controller 之間的交互:
Model是數據管理者,可理解為它直接和數據庫打交道,Model 和 View 應該是一種同步關系,即不管任何時候只要 Model 的數據發生改變,View 顯示的內容也應該發生改變。我們可關注 Model 的值發生改變而不用關心 Model 的網絡請求是否結束了,Controller 根本不關心Model從哪里拿數據,Controller 的責任是把Model 最新的值賦值給View,Controller 關注的是Model 的值是否發生改變
MVC 的設計思想其實就是在后台代碼中對各個階段代碼工作內容不同的分工,從而在實際編碼過程中,能夠達到專人負責專項的工作理念和開發人員的分工合作。
MVC與編程語言無關,與具體語言的語法規則無關。
核心理念:單一職責,分工協作
優點:
- 更好的開發效率
- 更好的可維護性
MVVM 的誕生:
需要數據有M, 需要界面有V,把M 的數據給V顯示,所以有 C,但是我們忽略了一個重要的操作:數據解析,在MVC 時代手機API數據比較簡單,很可能一步就解決,那時把數據解析交給 Controller完成,但是現在手機API 數據越來越復雜,數據解析就沒有那么簡單了,如果繼續按照MVC 的設計思路,將數據解析放到C完成,C 將會變得相當臃腫。Controller 設計出來並不是處理數據解析的,而是:1、管理自己的生命周期, 2、處理 Controllder 之間的跳轉, 3、 實現 Controller 容器。在MVC 沒有誰是負責處理數據解析的,那么將由誰負責處理數據解析呢?沒有就創建一個新的類出來,開發者們專門為數據解析創建出一個新的類:ViewModel,於是 MVVM就誕生了
MVVM 的實現:
在MVVM Controller 的存在感被完全降低了,VM 的出現是Controller 存在感降低的原因。VM 先拿到原始數據,經過數據解析,把結果給Controller,因為 Controller只需要 數據解析的結果而不關心過程,所以相當於 VM 把如何解析Model 的數據封裝起來了,C 根本不需要知道這個M 的存在,前提是有VM, 一旦在實現 Controller中遇到任何與Model(數據)有關的問題就找 VM
3、 數據雙向綁定的原理
雙向綁定是什么意思?

雙向是指ViewModel中的data部分和View之間的雙向關系。
正向:數據驅動頁面
反向:頁面更新數據
綁定是指自動化處理,data改變了view隨之改變,反之也是。
不用像傳統方式那樣,通過onChange事件獲取用戶輸入,然后再通過改變innerHtml修改顯示。
雙向綁定的原理是什么?可以寫出來嗎?
雙向綁定都是依賴ES5中一個重要的API,Object.defineProperty。
正向:
Object.defineProperty的作用:
監聽到 data的變化,監聽到變化后會有個回調函數,在定義的時候直接寫回調函數,在Object.defineProperty回調函數寫明 view 和 data 的關聯關系,后續中data有變化就會自動根據你寫的關聯處理修改View的顯示內容。直接操作修改data是因為 Object.defineProperty 有 set 屬性
反向:
在view中輸入內容時,通過 input 事件(比如 onchange),修改 data。只不過這件事不需要我們程序員自己去寫了,有些框架背后在做這件事,比如,在Vue框架中,可以使用V-Model方便的關聯view和data。
Object.defineProperty()
定義:
Object.defineProperty() 方法是在一個對象新定義一個屬性,或者修改一個對象的現有屬性,返回修改后的這個對象(換句話就是 修改一個對象返回一個修改后的對象)
語法:
Object.defineProperty(obj, prop, descriptor)
參數:
obj:要在其上定義屬性的對象。
prop:要定義或修改的屬性的名稱。
descriptor:將被定義或修改的屬性描述符。
返回值:
被傳遞給函數的對象。
Object.defineProperty 與 Reflect.defineProperty 的區別:
Object.defineProperty是 ES5 的用法,返回的是一個對象
Reflect.defineProperty是ES6 的用法,返回的是一個布爾值
Object.defineProperty缺點:
- 深度監聽,需要遞歸到底,一次性計算量大
- 無法監聽新增/刪除屬性(所以需要 vue.set vue.delete 實現新增/刪除屬性)
- 無法監聽原生數組,需要特殊處理
4、 設計模式 (觀察者模式)

1. 監聽者 (Observer): 有一個監聽者(Observer),監聽 data發生的變化, (通過Object.defineProperty 的get 屬性重新遍歷,修改 get 和 set 操作,還會有一個遞歸的操作,如果操作的是一個子對象,會對這個子對象重新進行遞歸遍歷一遍保證所有的key值都是有 observer 對象的。)
2. 觀察者列表 (Dep): 數據的變化會觸發 Object.defineProperty 對象的set 屬性,set 會執行對觀察者列表的觸發,通知觀察者列表(Dep)
3. 列表會有一個更新函數,通知了它們,它們會自動調用 updated 更新函數,也就是 Dep 調用了 回調,這個 回調是 觀察者 (watcher)給的
4. 觀察者(watcher): watcher 拿到更新后的數據就可以更新到 view了
5. 觀察者列表的updated 是怎么傳進去的,是通過 watcher 的訂閱,watcher 往觀察者列表添加新的內容(監聽完后會有一步實例化 watcher 對象,想對A 操作,observer 完后會手動調用一下watcher,實例化watcher,watcher 會調用 get,檢測watcher全局變量是否有值,有值的話會調用 deep.target,把內容往 Dep添加)
