【官方地址】
https://wiki.appcelerator.org/display/guides/Mobile+Best+Practices
指南的目的
Titanium Mobile是一个相对年轻,不断变化的平台. 本文所提到最佳实践也有可能会发生变化,也不可能网罗所有在创建一个庞大的Titanium应用的所有必要知识。本文的目的是站在更高层次上来判断,什么是当前在Titanium应用(或者一般的JavaScript开发)开发中,认为是好的(也有不好的)实践。
指南的概要
在Titanium Mobile应用中应该:
- (1)小心不要去破坏全局变量
- (2)在单JavaScript上下文中运行
- (3)使用CommonJS modules创建模块化,面向对象的代码
- (4)延迟加载JavaScript文件直到你真正需要它
- (5)使用适当的方法管理内存从而达到避免内存泄漏
真的?只有这些吗?
是的. 开发一个应用有很多方式,比如使用MVC或者MVVM机构,或者你自己选定的,你自己开发的一个框架。在本指南文中我们着眼的是,为了开发出一个稳定的,高性能的Titanium Mobile应用,我们需要考虑的基本需要。你自己的应用的结构,业务逻辑以及规约等可能有所不同,我们不会在本文中涉及到任何这样的内容。
(1)要注意不要去破坏全局变量
在JavaScript的程序中存在全局变量,那些被定义在全局的任何一个变量都将成为全局变量的一个属性。在Titanium Mobile中,全局变量在app.js通过this可用。
任何在app.js中定义的变量,都将在真个应用中全局可用。借助于Ti.include导入外部文件的时候,就会有可能发生变量名冲突。由于这个还有其他的一些原因,Ti.include是不被推荐使用的,而取代他的是秉承了CommonJS Module风格的"require"函数。
为了防止破坏app.js中的全局变量,你必须通过JavaScript闭包在需要使用的范围内使用。一个使用闭包来避免破坏全局变量的简单例子就是使用自调用函数。
app.js
(function() {
var privateData = 'tee hee! can\'t see me!';
})();
alert(privateData); //undefined in the global scope
当把代码分离成外部文件时,CommonJS Module提供的require函数,能为全局范围或者模块范围注入附加的功能。这个技巧将会在本文的后面具体解释。
(2)应该在单JavaScript上下文中运行
在上边的部分,我们讨论了JavaScript应用的全局变量。在Titanium Mobile中,能够通过指定一个JavaScript文件(与当前文件有关)给window的url属性来创建window。
当调用window的open方法时,关联的JavaScript文件将会被执行,创建了第二个“执行上下文”也就是一个新的scope。除非是一些少见的情况外,这种多个被激活的JavaScript环境是应该避免的。
这种多个执行上下文会导致一些问题,因为没有那个scope能在其他的scope中可见,这样就意味着不通过使用应用级别的自定义事件的笨办法(使用Titanium.App addEventListener 和fireEvent)在上下文之间传递数据不可能的。他们也会导致循环引用以及内存溢出。这里也存在一个生命周期的问题,当被指定的JavaScript文件被执行,生命周期将变得不清晰!
除过有一些原因需要使用的这种方法,比如“应用中的应用”,需要每个新window在全局上下文中没有依赖,而且是“clean slate”。一般来说都不会用到通过URL指定的窗口。
(3)使用CommonJS modules创建模块化,面向对象的代码
除过应用的逻辑都应该强制分割,分开到JavaScript文件.不是所有的JavaScript引用需要使用实例化对象,但是他们都应该利用commonjs模块标准。CommonJS module提供一个拥有很好的接口的沙盒JavaScript模块,通过在CommonJS module 文件中的exports对象提供。下边是一个在Titanium Mobile中使用CommonJS module的简单例子。
A simple CommonJS module, in lib/maths.js in the Resources directory
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;
};
Sample usage in app.js
var maths = require('lib/maths');
var sum = maths.add(2,2,2,2,2);
//sum is 10
一个CommonJS module的实例化对象大概下边这样的:
lib/geo.js - A module containing instantiable objects
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;
Sample usage in app.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);
在Titanium Mobile应用中创建对象的时候,也必须遵守一般的编程最佳实践,包括面向对象(OO)。在这一节,我们进到另外一个例子中。在Titanium Mobile应用中共通的是都需要创建自己的自定义界面组件。一个常见的做法是扩展内置对象实现继承关系。
在标准的JavaScript中,一个完美合适的实践是,在不知道的行为下,他能使用proxy对象(通过Ti.UI.createView返回的对象或者类似的)获得结果。创建自定义组件的合理方法是把proxy和一般的JavaScript对象关联,比如以下代码:
ui/ToggleBox.js - A custom check box
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;
Sample usage in app.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文件直到他们需要之前是没有被加载的。
Lazy script loading in app.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.这样就能保证这些对象被垃圾回收,从而释放关联的本地对象.
例:
Nulling out object references
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