上篇(http://www.hightopo.com/blog/194.html)咱們經過定製了CPU和內存展現界面,體驗了HT for Web經過定義矢量實現圖形繪製與業務數據的代碼解耦及綁定聯動,這類案例後續文章還會繼續以便你們掌握更多的矢量應用場景,本篇咱們先切換個話題,談談模型-視圖-事件之間的關係。html
http://www.hightopo.com/guide/guide/core/beginners/examples/example_overview.htmlweb
圖形組件設計架構上主要就是在規劃Data模型,View視圖和Event事件之間的關係,這些年業界逐漸將各類GUI設計模式提煉成理論歸類,MVC、MVP和MVVM的主要大類常被統稱爲MV*,有不少文章進行各類設計模式的定義和比較,本篇不打算深刻展開理論的討論,不一樣圖形組件設計架構都會有不少差別,持續發展的組件其實每時每刻都在進行着各類設計上的改進,相信有不少不錯的組件已經創新出了更多新的更實用的設計模型,只不過還未被提煉到理論高度進行歸類讓世人知曉,所以過細去定義什麼是P,什麼是VM,哪一個功能應該寫在哪一個部分纔算合理我以爲是沒太大意義的,只要不斷改進產品,團隊能更好維護擴展,用戶易學易用就夠了,理論高度留給Martin Fowler這類神級大師去定義。chrome
提到Martin Fowler由於他的《GUI Architectures》和《Presentation Model》是我較早見到將MVC和MVP理清的文章,從實現角度其實幾十年前蘋果用於開發Mac OS X的Cocoa Bindings技術已採用了相似的設計,而且Objective-C語言的Key-Value Coding和Key-Value Observing機制,加上XCode工具的可視化支持,能夠說多年來早已讓衆多開發者不知不覺在享受這些設計模型能帶來的開發力。Java的Swing界面一直飽受詬病,但其實很早就有JGoodies這樣優秀項目,Swing本就不算大衆,瞭解JGoodies更是小衆,而更少人瞭解JGoodies Binding這多年前就實現得很是不錯的MVP架構封裝,有興趣的讀者可看看JGoodies這篇06年的PPT《Desktop Patterns and Data Binding》。設計模式
Adobe的Flex和微軟的Silverlight/WPF本被業界寄予厚望,沒想這哥倆如匆匆過客被老東家拋棄了,但他們仍是推進了MVP和MVVM設計模式的普及,現在HTML5領域的KnockoutJS、Backbone.js、AngularJS、PureMVC、Ember.js等衆多MV*框架若是雨後春筍般崛起,甚至須要有人專門維護個TodoMVC的網站來:Helping you select an MV* framework!瀏覽器
HT自己也是一套MV*的框架,但咱們培訓客戶時不多過細討論設計模式,在我看來好的組件封裝應該沒必要讓用戶糾結於你的設計模式,用戶幾個月不用你的框架後,依然能快速上手沒必要有一個重寫學習的過程,這是咱們最求的理想框架,從這個角度說目前不多有圖形框架能讓咱們滿意,相信不少人有相似痛苦的經歷,一段時間不用某套框架後,要用時徹底忘記如何入手,Swing老手不看老代碼不知如何對JTree和JTable添加數據,Flex老手一會兒想不起來invalidateProperties,invalidateSize和 invalidateDisplayList這幾個自定義組件必掌握函數的細節,SL/WPF老手想不起來定義一個DependencyProperty屬性除了AffectsRenderer和AffectsMeasure還有多少要考慮的因素,上段提到的一堆新興的HTML5界MV*框架,相信更少有人敢說熟練精通,你可能在某個項目中用了好幾個月甚至一兩年,但一段時間不用你很容易忘記,所以對喊出精通缺少勇氣了,我以爲這不是你們不聰明不勤奮,而是目前的這些框架真還沒作到足夠好,咱們一直努力讓HT朝咱們以爲滿意的方向發展,之後文章我再展開討論HT如何設計讓用戶不健忘的API接口。架構
回到今天模型-視圖-事件的話題,Data和View分離後必然須要有Event事件的監聽和派發機制來創建起數據綁定,我控制慾比較強不是很喜歡AngularJS那種dirty checking的機制,有事件變化我但願立刻被通知到,作我該作的處理,至於有人擔憂性能問題那是多慮了,圖形組件發展這麼多年已積累無數成熟技巧來規避事件的性能問題。mvc
性能問題倒不用擔憂,畢竟這方面任務大部分狀況都是交由框架實現者去考慮,但不須要用戶深刻了解框架的實現細節,並不意味着用戶能夠徹底不關係基本架構脈絡,框架應用者仍是有必要了解模型-視圖-事件之間的引用關聯關係,不然容易出現內存泄露的問題,之前經歷過一個客戶團隊設計的客戶端框架,可管理全部界面的窗口,結果出現老是OOM的內存溢出,幫他們檢查後發現,他們有個全局的WindowManager對象,在每一個窗口建立時都會添加對窗口的引用,這樣當然貌似很強大,全局均可以控制全部界面窗口,但由於絕大多數開發人員,不會在窗口關閉要銷燬時主動去刪除全局WindowManager對象的引用,進而致使了全部窗口都能被全局對象引用到而沒法垃圾回收,所以框架的使用者仍是有必要多框架的機制有所瞭解才能避免這類的內存泄露問題。app
不少狀況下內存泄露不是長期的運行也很難發覺,但對於HT的Graph3dView這種基於WebGL的3D組件問題尤其明顯,由於大部分瀏覽器對單個頁面能運行的WebGL上下文是有限制的,例如PC上的chrome或firefox也就運行十五六個,手機平板等移動終端會更受限,所以若是出現內存泄露老的上下文沒關閉,超越上限時就會出現類型」Too many active WebGL contexts. Oldest context will be lost.」的異常。框架
如下我對《HT入門手冊》的第一個例子作個擴展,對工具條增長了以下代碼邏輯的三個按鈕,第一個按鈕一會兒建立了20個新的Tab頁,每一個Tab頁包含一個Graph3dView組件,另外兩個按鈕實現刪除部分頁籤的功能。dom
{
label: 'Create 20', action: function(){ for(var i=0;i<20;i++){ var tab = new ht.Tab(); tab.setName('tab-'+i); tab.setClosable(true); tabView.getTabModel().add(tab); var g3d = new ht.graph3d.Graph3dView(dataModel); g3d.name = 'g3d-' + i; window['g3d-' + i] = g3d; tab.setView(g3d); } } }, { label: 'Destroy 5', action: function(){ var emptyModel = new ht.DataModel(); tabView.remove('tab-5'); window['g3d-5'].setDataModel(emptyModel); delete window['g3d-5']; this.disabled = true; } }, { label: 'Destroy 6-10', action: function(){ for(var i=6; i<=10; i++){ tabView.remove('tab-' + i); var emptyModel = new ht.DataModel(); window['g3d-' + i].setDataModel(emptyModel); delete window['g3d-' + i]; } this.disabled = true; } }
點擊建立20個頁籤的按鈕分別打開頁籤以後系統的內存對象引用關係以下圖所示:
由於dataModel做爲全局對象被window應用着,並且其餘新建立的頁籤中的Graph3dView都綁定了該數據模型,框架使用者應該瞭解,各類組件都對dataModel數據模型添加了事件監聽,其實數據模型並不知道各類View的存在,數據模型僅遵循有數據變化後將事件正確的派發給全部消費者,而這20個Graph3dView就是其中的消費者,而Graph3dView中每一個有都有一個WebGL的context上下文,於是造成了一條從全局window到dataModel數據模型,再到Graph3dView組件,最後到WebGL上下文的引用關係網,這樣天然若是咱們不主動斷開這個關係,哪怕Tab頁籤被關閉銷燬,Graph3dView依然還會存在系統內存的問題(這個例子咱們爲了測試方便其實還在window上直接引用了Tab和Graph3dView對象)。
所以由以上視頻你會發如今chrome下當點擊到第16個包含Graph3dView的頁籤後就出現了」Too many active WebGL contexts. Oldest context will be lost.」的異常,在WebGL中可經過對Canvas添加webglcontextlost的事件監聽可判斷本身的上下文被銷燬了,並可經過添加webglcontextrestored的事件監聽在瀏覽器資源足夠時從新進行恢復。
在咱們這個案例中要讓系統資源恢復,咱們必須讓過多的Tab頁籤中的Graph3dView被完全回收,所以工具條上的另外兩個按鈕從代碼邏輯可知,咱們將Graph3dView設置了一個新的空得DataModel數據模型,使其斷開了和全局window.dataModel的引用,固然Tab頁籤也得刪除,從以上視頻中也能夠看得出當咱們銷燬了部分Tab頁籤後就能獲得webglcontextrestored的事件恢復,所以第一個」HT for 3D Web」的頁籤經歷了webglcontextlost和webglcontextrestored的過程。
啓動初始化時只有」HT for 3D Web」的第一個頁籤,所以經過Chrome的Debug Profiles可查看到ht.graph3d.Graph3dView的Objects Count項只有1,經過Profiles的Retainers咱們還能夠清楚的掌握目前達到那些對象引用了Graph3dView對象:
當點擊構建20個頁籤按鈕後,Profiles能看到Objects Count爲21:
當咱們點擊兩個刪除按鈕銷燬6個Tab頁籤後發現,Objects Count降低到了15:
最後能夠發現第一個HT for 3D Web的頁籤浴火重生了
這個案例只是爲了測試方便所以將dataModel對象做爲全局變量,因此引起了一些列內存泄露的資源不足問題,通常項目應用中不用的組件不須要考慮這麼複雜,例如還須要斷開dataModel引用這些步驟,常規應用場景中例如一個對話框打開後,通常數據模型和視圖組件都在這個對話框範圍內相互引用,只要確保不出現上文提到的有全局引用能影響這個對話框內的某個對象,那麼你在使用完該對話框後不須要作任何處理,那一堆的對象哪怕他們之間引用再複雜甚至互相應用,反正沒有全局對象可以再引用到他們,他們通通都會被銷燬。
http://www.hightopo.com/guide/guide/core/beginners/examples/example_overview.html
總結下本篇的兩個觀點:
一、再好的封裝設計也須要使用者掌握基本的架構脈絡,就像再好的車你也得學會開學會基本的保養,什麼都不學的話,再好的框架也會像好車同樣被你開壞
二、不要害怕MV*的事件和引用關係,理清事件機制和對象引用關係後,你能夠精確掌控任什麼時候刻的任何內部細節,這點主要針對設計框架者而言,使用者應該大膽的擁抱MV*的框架,性能和各類潛在的內存問題放心的交給框架去解決