上篇咱们经过定制了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*的框架,性能和各类潜在的内存问题放心的交给框架去解决