Objective of the Guidelines

Titanium Mobile is a relatively young and constantly changing platform. The best practices delineated here are subject to change, and may not encompass all the knowledge necessary to build a great Titanium application. The goal of this document is to lay out, at a high level, what are currently known to be good (and some bad) practices in Titanium Mobile (and JavaScript in general) applications.

The Guidelines in Brief

Titanium Mobile applications should:

Seriously? That's it?

Yes. There are many different ways to build an application - using an MVC or MVVM architecture, maybe a framework of your own choosing or one you've built yourself. The guidelines in this document are considered the bedrock requirements for a stable and performant Titanium Mobile application. Your own application structures, business logic, and conventions may vary, and we do not endeavor to impose any such conventions in this document.

The Guidelines Explained

Applications shall take care not to pollute the global object

In JavaScript, there is a global object for the application - any variables declared in the global scope will become properties of this global object. In Titanium Mobile, the global object is available as this in app.js.

Any variables declared in app.js shall be globally available in the entire application. This can cause collisions if external scripts are included into this context via Ti.include. For this and other reasons, use of Ti.include is discouraged, and will be replaced in the future by the CommonJS module specification's require function.

To prevent polluting the global object in app.js, you must use the only scope available in JavaScript - a closure. A simple way to use a closure to avoid polluting the global scope is to use a self-calling function:

app.js
?
(function() {
var privateData = 'tee hee! can\'t see me!';
})();
alert(privateData); //undefined in the global scope

When separating code into external files, the require function provided by the CommonJS module spec should be used to inject additional functionality into the global scope or the scope of a module. This technique will be explained later on in the document.

Applications shall run in a single JavaScript context

In the previous section, we discussed the global scope of a JavaScript application. With Titanium Mobile, it is possible to create a window with a url property set to a path to a Javascript file (relative to the current file). When the window's open method is called, the associated JavaScript file is evaluated, creating a secondary "execution context" and, thus, a new scope. Except in rare cases, this multiple active JavaScript environment should be avoided.

These multiple execution contexts cause problem because no scope has visibility of any other, meaning that sharing data between contexts is not possible without the ungainly use of application-level custom events (using Titanium.App addEventListener and fireEvent). They can also lead to circular references and likely memory leaks. There are lifecycle issues too, where it becomes unclear when the code for a given JavaScript file has been evaluated.

While there are a few reasonable use cases for this approach, such as an "app within an app" where every new window requires a "clean slate" with no dependencies on the global context, normally windows with URLs should not be used.

Applications shall be modular and object-oriented using CommonJS modules

Separation of concerns should be enforced by dividing application logic into separate JavaScript files and objects, adhering to the principle of cohesion. Not all JavaScript applications need to use instantiable objects - but all should make use of the CommonJS module standard. The CommonJS module spec provides for sandboxed JavaScript modules with a well-defined public interface, exposed as the exports object within a CommonJS module file. A sample usage of the CommonJS module spec in Titanium Mobile is below.

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

An instantiable object in a CommonJS module would look like this:

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);

When creating objects in Titanium Mobile applications, one must adhere to general programming best practices, including object orientation. In this section, we will go into one more example. A common need in Titanium Mobile applications is to create custom user interface components. A common practice for extending built-in objects is parasitic inheritance. While a perfectly valid practice in standard JavaScript, it can result in unpredictable behavior in Titanium Mobile when using proxy objects (objects returned by Ti.UI.createView and similar). A reasonable approach to creating a custom component would be to associate a proxy with a normal JavaScript object, as in the below:

ui/ToggleBox.js - A custom check box
?
function ToggleBox(onChange) {
this.view = Ti.UI.createView({backgroundColor:'red',height:50,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);

Applications shall defer script loading until absolutely needed

One of the bottlenecks of a Titanium application is JavaScript evaluation. This is particularly the case for Android, although the V8 runtime provides substantial improvements for this issue compared with Rhino. For that reason, to speed the startup and responsiveness of your application, the developer should avoid loading scripts until they are absolutely needed. As in the following application, which has three windows to be opened in succession on a click (touch) event, note that the dependent JavaScript for each window is not loaded until absolutely necessary.

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();
});
});

Applications shall take reasonable steps to manage memory

In very large applications, it occasionally becomes necessary to force Titanium to clean up resources. To do this, the developer has a few things to track:

Example:

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

发表回复