Titanium Mobile 最佳实践
【官方地址】https://wiki.appcelerator.org/display/guides/Mobile+Best+Practices
指南的目的
TitaniumMobile是一个相对年轻,不断变化的平台.本文所提到最佳实践也有可能会发生变化,也不可能网罗所有在创建一个庞大的Titanium应用的所有必要知识。本文的目的是站在更高层次上来判断,什么是当前在Titanium应用(或者一般的JavaScript开发)开发中,认为是好的(也有不好的)实践。
指南的概要
在TitaniumMobile应用中应该:
- (1)小心不要去破坏全局变量
- (2)在单JavaScript上下文中运行
- (3)使用CommonJSmodules创建模块化,面向对象的代码
- (4)延迟加载JavaScript文件直到你真正需要它
- (5)使用适当的方法管理内存从而达到避免内存泄漏
真的?只有这些吗?
是的.开发一个应用有很多方式,比如使用MVC或者MVVM机构,或者你自己选定的,你自己开发的一个框架。在本指南文中我们着眼的是,为了开发出一个稳定的,高性能的TitaniumMobile应用,我们需要考虑的基本需要。你自己的应用的结构,业务逻辑以及规约等可能有所不同,我们不会在本文中涉及到任何这样的内容。
(1)要注意不要去破坏全局变量
在JavaScript的程序中存在全局变量,那些被定义在全局的任何一个变量都将成为全局变量的一个属性。在TitaniumMobile中,全局变量在app.js通过this可用。
任何在app.js中定义的变量,都将在真个应用中全局可用。借助于Ti.include导入外部文件的时候,就会有可能发生变量名冲突。由于这个还有其他的一些原因,Ti.include是不被推荐使用的,而取代他的是秉承了CommonJSModule风格的"require"函数。
为了防止破坏app.js中的全局变量,你必须通过JavaScript闭包在需要使用的范围内使用。一个使用闭包来避免破坏全局变量的简单例子就是使用自调用函数。
app.js
(function() { var privateData = 'tee hee! can\'t see me!'; })(); alert(privateData); //undefined in the global scope
当把代码分离成外部文件时,CommonJSModule提供的require函数,能为全局范围或者模块范围注入附加的功能。这个技巧将会在本文的后面具体解释。
(2)应该在单JavaScript上下文中运行
在上边的部分,我们讨论了JavaScript应用的全局变量。在TitaniumMobile中,能够通过指定一个JavaScript文件(与当前文件有关)给window的url属性来创建window。
当调用window的open方法时,关联的JavaScript文件将会被执行,创建了第二个“执行上下文”也就是一个新的scope。除非是一些少见的情况外,这种多个被激活的JavaScript环境是应该避免的。
这种多个执行上下文会导致一些问题,因为没有那个scope能在其他的scope中可见,这样就意味着不通过使用应用级别的自定义事件的笨办法(使用Titanium.AppaddEventListener和fireEvent)在上下文之间传递数据不可能的。他们也会导致循环引用以及内存溢出。这里也存在一个生命周期的问题,当被指定的JavaScript文件被执行,生命周期将变得不清晰!
除过有一些原因需要使用的这种方法,比如“应用中的应用”,需要每个新window在全局上下文中没有依赖,而且是“cleanslate”。一般来说都不会用到通过URL指定的窗口。
(3)使用CommonJSmodules创建模块化,面向对象的代码
除过应用的逻辑都应该强制分割,分开到JavaScript文件.不是所有的JavaScript引用需要使用实例化对象,但是他们都应该利用commonjs模块标准。CommonJSmodule提供一个拥有很好的接口的沙盒JavaScript模块,通过在CommonJSmodule文件中的exports对象提供。下边是一个在TitaniumMobile中使用CommonJSmodule的简单例子。
AsimpleCommonJSmodule,inlib/maths.jsintheResourcesdirectory
var privateData = 'private to the module\'s sandbox, not available globally'; //any property attached to the exports object becomes part of the module's public interface exports.add = function() { var result = 0; for (var i = 0, l = arguments.length;i<l;i++) { result = result+arguments[i]; } return result; };
Sampleusageinapp.js
var maths = require('lib/maths'); var sum = maths.add(2,2,2,2,2); //sum is 10
一个CommonJSmodule的实例化对象大概下边这样的:
lib/geo.js-Amodulecontaininginstantiableobjects
function Point(x,y) { this.x = x; this.y = y; } function Line(start,end) { this.start = start; this.end = end; } //create public interface exports.Point = Point; exports.Line = Line;
Sampleusageinapp.js
var geo = require('lib/geo'); var startPoint = new geo.Point(1,-5); var endPoint = new geo.Point(10,2); var line = new geo.Line(startPoint,endPoint);
在TitaniumMobile应用中创建对象的时候,也必须遵守一般的编程最佳实践,包括面向对象(OO)。在这一节,我们进到另外一个例子中。在TitaniumMobile应用中共通的是都需要创建自己的自定义界面组件。一个常见的做法是扩展内置对象实现继承关系。
在标准的JavaScript中,一个完美合适的实践是,在不知道的行为下,他能使用proxy对象(通过Ti.UI.createView返回的对象或者类似的)获得结果。创建自定义组件的合理方法是把proxy和一般的JavaScript对象关联,比如以下代码:
ui/ToggleBox.js-Acustomcheckbox
function ToggleBox(onChange) { this.view = Ti.UI.createView({backgroundColor:'red',height:50,width:50}); //private instance variable var active = false; //public instance functions to update internal state, execute events this.setActive = function(_active) { active = _active; this.view.backgroundColor = (active) ? 'green' : 'red'; onChange.call(instance); }; this.isActive = function() { return active; }; //set up behavior for component this.view.addEventListener('click', function() { this.setActive(!active); }); } exports.ToggleBox = ToggleBox;
Sampleusageinapp.js
var win = Ti.UI.createWindow({backgroundColor:'white'}); var ToggleBox = require('ui/ToggleBox').ToggleBox; var tb = new ToggleBox(function() { alert('The check box is currently: '+this.isActive()); }); tb.view.top = 50; tb.view.left = 100; win.add(tb.view);
(4)延迟加载JavaScript文件直到你真正需要它
Titanium应用的一个瓶颈就是JavaScript的执行。Android就是一个具体的例子。尽管它采用了V8引擎先比Rhino来说确实对此问题有所改善。由于这个原因,加速你的引用的启动和响应,开发者应该在真正需要加载他们之前尽量避免加载代码。
在下边这个例子中,当点击事件成功后,将打开三个窗口,注意每个窗口的JavaScript文件直到他们需要之前是没有被加载的。
Lazyscriptloadinginapp.js
//muse be loaded at launch var WindowOne = require('ui/WindowOne').WindowOne; var win1 = new WindowOne(); win1.open(); win1.addEventListener('click', function() { //load window two JavaScript when needed... var WindowTwo = require('ui/WindowTwo').WindowTwo; var win2 = new WindowTwo(); win2.open(); win2.addEventListener('click', function() { //load window three JavaScript when needed... var WindowThree = require('ui/WindowThree').WindowThree; var win3 = new WindowTwo(); win3.open(); }); });
(5)使用适当的方法管理内存从而达到避免内存泄漏
在大规模的应用中,偶尔会需要Titanium被迫清理资源。为此,开发者应该有一些途径:
- 关闭window.这样就能使Titanium释放一些预留给添加在window中的view对象的资源.
- 在不需要的时候去除全局事件handler。引用对象在闭包中被保留,否则这也是和浏览器中大型JavaScript应用中共通的内存溢出问题。还有一些共用全局事件handler包括Ti.App.addEventListener(全局消息系统),地理位置信息事件,还有手机朝向变化事件。
- 将proxy对象设置为null.这样就能保证这些对象被垃圾回收,从而释放关联的本地对象.
例:
Nullingoutobjectreferences
var win = Ti.UI.createWindow(); var myBigView = new BigView(); win.add(myBigView.view); win.open(); //...at some point in the future... win.remove(myBigView); myBigView = null; //will cause view to be GC'ed