在開始介紹angular原理之前,我們有必要先了解下mvvm模式在angular中運用。雖然在angular社區一直將angular統稱為前端MVC框架,同時angular團隊也稱它為MVW(Whatever)框架,但angular框架整體上更接近MVVM模式。下面是Igor Minar發布在Google+ https://plus.google.com/+IgorMinar/posts/DRUAkZmXjNV的文章內容:
MVC vs MVVM vs MVP. What a controversial topic that many developers can spend hours and hours debating and arguing about.
For several years +AngularJS was closer to MVC (or rather one of its client-side variants), but over time and thanks to many refactorings and api improvements, it’s now closer to MVVM – the $scope object could be considered the ViewModel that is being decorated by a function that we call a Controller.
Being able to categorize a framework and put it into one of the MV* buckets has some advantages. It can help developers get more comfortable with its apis by making it easier to create a mental model that represents the application that is being built with the framework. It can also help to establish terminology that is used by developers.
Having said, I’d rather see developers build kick-ass apps that are well-designed and follow separation of concerns, than see them waste time arguing about MV* nonsense. And for this reason, I hereby declare AngularJS to be MVW framework – Model-View-Whatever. Where Whatever stands for “whatever works for you”.
Angular gives you a lot of flexibility to nicely separate presentation logic from business logic and presentation state. Please use it fuel your productivity and application maintainability rather than heated discussions about things that at the end of the day don’t matter that much.
在文中特別指出angular在多次的API重構和改善,它越來越接近於MVVM模式,$scope可以被認為是ViewModl,而Controller則是裝飾、加工處理這個ViewModel的JavaScript函數。作者更希望大家關注於實現一個成功的,具有好的設計以及遵循“分離關注點”原則的應用程序,而不是去爭論MV*,所以他將angular稱為MVW框架,是什么並不重要,只要適合你的應用就行。
MVVM模式是Model-View-ViewMode(模型-視圖-視圖模型)模式的簡稱,其最早出現在微軟的WPF和Silverlight框架中。MVVM模式利用框架內置的雙向綁定技術對MVP(Model-View-Presenter)模式的變型,引入了專門的ViewModel(視圖模型)來實現View和Model的粘合,讓View和Model的進一步分離和解耦。MVVM模式的優勢有如下四點:
- 低耦合:View可以獨立於Model變化和修改,同一個ViewModel可以被多個View復用;並且可以做到View和Model的變化互不影響;
- 可重用性:可以把一些視圖的邏輯放在ViewModel,讓多個View復用;
- 獨立開發:開發人員可以專注與業務邏輯和數據的開發(ViewModel),界面設計人員可以專注於UI(View)的設計;
- 可測試性:清晰的View分層,使得針對表現層業務邏輯的測試更容易,更簡單。
下面是angular中關於MVVM模式的運用:
在angular中MVVM模式主要分為四部分:
- View:它專注於界面的顯示和渲染,在angular中則是包含一堆聲明式Directive的視圖模板。
- ViewModel:它是View和Model的粘合體,負責View和Model的交互和協作,它負責給View提供顯示的數據,以及提供了View中Command事件操作Model的途徑;在angular中$scope對象充當了這個ViewModel的角色;
- Model:它是與應用程序的業務邏輯相關的數據的封裝載體,它是業務領域的對象,Model並不關心會被如何顯示或操作,所以模型也不會包含任何界面顯示相關的邏輯。在web頁面中,大部分Model都是來自Ajax的服務端返回數據或者是全局的配置對象;而angular中的service則是封裝和處理這些與Model相關的業務邏輯的場所,這類的業務服務是可以被多個Controller或者其他service復用的領域服務。
- Controller:這並不是MVVM模式的核心元素,但它負責ViewModel對象的初始化,它將組合一個或者多個service來獲取業務領域Model放在ViewModel對象上,使得應用界面在啟動加載的時候達到一種可用的狀態。
View不能直接與Model交互,而是通過$scope這個ViewModel來實現與Model的交互。對於界面表單的交互,通過ngModel指令來實現View和ViewModel的同步。ngModelController包含$parsers和$formatters兩個轉換器管道,它們分別實現View表單輸入值到Model數據類型轉換和Model數據到View表單數據的格式化。對於用戶界面的交互Command事件(如ngClick、ngChange等)則會轉發到ViewModel對象上,通過ViewModel來實現對於Model的改變。然而對於Model的任何改變,也會反應在ViewModel之上,並且會通過$scope的“臟檢查機制”($digest)來更新到View。從而實現View和Model的分離,達到對前端邏輯MVVM的分層架構。
angular中MVVM模式的實現,以領域Model為中心思維,遵循“分離關注點”設計原則,這也是與jQuery以DOM驅動的思維所不同之處。所以我們在做angular開發的時候應該謹記下面幾點:
絕不要先設計你的頁面,然后用DOM操作去改變它
在以往的jQuery開發中,我們會首先設計頁面DOM結構,然后在利用jQuery來改變DOM結構或者實現動態交互效果。因為jQuery是為DOM驅動而設計的,對於擁有大量復雜的前端交互的項目,JavaScript的邏輯變得越來越臃腫,交互邏輯分散各處。
在MVVM模式下的angular開發中, 我們首先需要在腦子里掛着Model的弦。不能老想着“我有XXX這個DOM,我希望讓它做XXX這種動態效果”,我們需要從要完成的目標開始思考我們需要或擁有怎么樣的Model數據,然后設計我們的應用, 最后才是設計視圖,並用$scope來粘合它們。
Directive不是封裝jQuery代碼的“天堂”
如上條所述,我們不能一開始就去想如何利用DOM操作的方法去實現應用目標,然后“冠冕堂皇”的寫上一堆jQuery的代碼,並將其封裝到angular的directive中,最后不得不加上$scope.$apply()來通知angular你的ViewModel的改變,需要啟動“臟檢查機制”來更新你的改變到View。作者在多個客戶項目中看見這種將Directive作為封裝jQuery代碼“天堂”的例子,其實對於這類問題,大部分情況下,我們都可以用很少了angular代碼將其重構為真正的angular way。特別在ng社區經常看見在angular directive中利用jQuery的on方法綁定click、keydown、blur等事件的代碼,大部分情況我們都能以對應的ng事件(ngClick、ngChange、ngBlur)來重構它們。
對於這類問題,首先我們應該盡量嘗試復用angular的內置指令,以真正的angular way去思考我們的問題,請慎重的引入jQuery的DOM方法和操作。
關於angular MVVM模式的資料,你還可以參考視頻:https://frontendmasters.com/courses/angularjs-mvc-mvvm-mvwhatever/#v=ypur7bfbcq。