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