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

相关推荐