haXe是高级语言的高级语言,可以将一个haXe程序转换为其它的高级语言,如JS。haXe有很多优点,比如,强类型、泛型、内联、宏、动态语言特性、命名空间……,用haXe写程序比用JS写程序舒服得多。本文尝试使用haXe开发js程序,搭建一个前端mini型mvc例子。
语言:haXe。haXe编译器可以把haXe程序编译成js程序。
开发工具:FlashDevelop。
haXe的简介及安装见《html5 canvas 版 hello world! 暨haXe简介》。开发之前,需要修改Flash Develop配置,将Tools->Program Setting->HaXeContext->Disable Compiler-based Completion 设置为 true。否则的话,haXe的自动完成会很痛苦。
项目结构如上图。其中:index.html是演示页面。页面中引用的jsTest.js就是本haXe项目所生成的js文件。js程序的入口就是Main.hx中的main方法(后文详述) 。main方法查找本页面中的所有的haXe标签,根据标签的name,分派出不同的Controller出来,然后根据haXe标签的map配置,把haXe所在节点的父节点及兄弟节点中id对应的节点映射到控制器里的字段。这样一来,一个页面可以对应0个、1个或多个Controller,每个页面片段也可以对应0个、1个或多个Controller,具有极大的灵活性。
有map的存在,在Controller那边,可以少写很多代码。map语法为:
(1)不同的map项由“;”分割;
(2)每个map项语法为“元素id:映射到字段的id”,如果省略掉“:”和之后的内容,则使用元素id作为映射字段的id。
这样一来,就可以把客户端的行为封装成强类型的、严格规划好命名空间的、有严格文档的haXe代码。开发前端时,只需要将不同的元素id映射到不同的Controller中即可实现不同的控制行为。
下面看看核心类:
(1)Page.hx
Page类封装了一些常用的方法:
package ;
import js.Dom;
#if hack
import js.HtmlDom;
import js.Document;
import js.HtmlCollection;
#endimport js.Lib;
class Page
{
public static function getControllers():HtmlCollection<HtmlDom>
{
return doc().getElementsByTagName("haXe");
}
public static inline function doc():Document
{
return Lib.document;
}
public static function getById(id:String,node:HtmlDom=null):HtmlDom
{
if (id == null || id == '') return null;
else if (node == null)
{
return doc().getElementById(id);
}
if (node.id == id) return node;
else
{
if (node.hasChildNodes() == true)
{
var item:HtmlDom = node.firstChild;
while (item != null)
{
if (item.id == id) return item;
item = item.nextSibling;
}
}
}
return null;
}
public static function getsByName(name:String):HtmlCollection<HtmlDom>
{
return doc().getElementsByName(name);
}
}
这里有一个技巧:FlashDevelop 对haXe自动完成支持的不完善。很多js命名空间下的类不能正确支持。比如,Page.hx文件,如果不引入js.HtmlDom等三个类(红色部分),则无法提供自动完成支持,而如果引入这三个类,则编译报错(这三个类已经在js.Dom中了)。解决方案就是使用一个haXe不认识的编译开关,比如“hack”,既能让FlashDevelop提供智能支持,又不会参与到编译过程中去。
(2)Controller.hx
Controller.hx 类主要完成map功能,代码为:
import js.Dom;
#if hack
import js.HtmlDom;
import js.Document;
import js.Event;
#endimport js.Lib;
class Controller
{
public function new(map:String = null, node:HtmlDom = null):Void
{
if (map == null) return;
var mapItems:Array<String> = map.split(';');
var dy:Dynamic = this;
for (i in 0 ... mapItems.length)
{
var itemTxt:String = mapItems[i];
var pairs:Array<String> = itemTxt.split(':');
if (pairs.length > 0)
{
var v0:String = StringTools.trim(pairs[0]);
var v1:String = v0;
if (pairs.length > 1)
{
v1 = StringTools.trim(pairs[1]);
}
if (v0.length > 0 && v1.length > 0)
{
untyped {
dy[v1] = Page.getById(v0,node);
}
}
}
}
}
}
(3)Test.hx
Test类为具体的Controller,为页面添加控制,代码为:
package orc;
import js.Dom;
#if hack
import js.HtmlDom;
import js.Document;
import js.Event;
#endimport js.Lib;
class Test extends Controller
{
private var btnSubmit:HtmlDom;
private var tbOutput:HtmlDom;
public function new(map:String = null, node:HtmlDom = null):Void
{
super(map, node);
var self:Test = this;
this.btnSubmit.onclick = function(e:Event):Void
{
self.tbOutput.innerHTML = "测试一下";
};
}
}
(4)Main.hx
Main类负责调度,且提供程序的入口,代码为:
package ;
import js.Dom;
#if hack
import js.HtmlCollection;
import js.HtmlDom;
import js.Document;
import js.Event;
#endimport js.Lib;
import orc.Test;class Main
{
static function main()
{
var ctrls:HtmlCollection<HtmlDom> = Page.getControllers();
if (ctrls != null)
{
var l:Int = ctrls.length;
for (i in 0 ... l)
{
handle(ctrls[i]);
}
}
}
static function handle(node:HtmlDom):Void
{
var className:String = node.getAttribute("name");
var map:String = node.getAttribute("map");
var root:HtmlDom = map != null ? node.parentNode: null;
switch(className)
{
case "orc.test":
new Test(map,root);
}
}
}
测试结果图:
所得到的JS文件大小为12K,其中约6K为haXe的js库。压缩后,文件大小为 4k。如果有多个页面的话,这个js文件会变大,但因为只下载一次,对于一般的网站或应用可以接受。
这样做有什么好处呢?
(1)强类型,可以充分利用IDE的提示和完成功能;
(2)可以把代码放在公共的地方,方便代码的积累。代码抽象好了,一般的应用,只映射一下就搞定了;
(3)代码结构清晰。
随着html5的到来,前端的代码量会越来越大,越来越复杂。js太灵活了不好用。拥抱haXe吧!