ActionScript到JavaScript的交叉编译

时间:2022-10-12 18:54:25

FalconJS (一个转换AS3为JavaSript的编译器的项目)的开发者发表了一系列博客,讲述了对类,继承,接口,包和命名空间这几个方面,ActionScript语言特性以及怎样在JavaScript里面对它们进行模拟,ActionScript到JavaScript的交叉编译等。对于希望学习JavaScript的AS3开发者们很有帮助。这是这个系列的全部四篇收录在这里啦:


编者:FalconJS (一个转换AS3为JavaSript的编译器的项目)的开发者发表了一系列博客,讲述了对类,继承,接口,包和命名空间这几个方面,ActionScript语言特性以及怎样在JavaScript里面对它们进行模拟,ActionScript到JavaScript的交叉编译等。对于希望学习JavaScript的AS3开发者们很有帮助。这是这个系列的第一篇。

《ActionScript 到JavaScript的交叉编译》由我之前发过的有关FalconJS的文章推测,好像很多的ActionScript开发人员对学习更多有关ActionScript到JavaScript的交叉编译感趣。

下面这些可能是你碰到过的一些问题:

  • 1. 这是怎么工作的呢?
  • 2. 从ActionScript到JavaScript的交叉编译是否真的可能呢?
  • 3. 这其中的难题是什么呢?
  • 4. 他的局限性在哪里呢?
  • 5. 什么样的ActionScript特性不能被交叉编译?
  • 6. 对一个”Hello World”程序而言,我是否将会得到一个17000行的JavaScript的程序代码?
  • 7. 哪种ActionScript程序代码会增加生成的JavaScript程序的大小,或者降低程序的性能?
  • 8. 你怎样写你的ActionScript代码,而这些代码要能够避免代码膨胀以及性能上的降低?

 

可惜的是我不能对有关FalconJS的任何特性作出评论,但是我可以回答上面的一些问题。所以我的计划是:这篇文章和接下来的文章我将试着给你一个简短的有关ActionScript到JavaScript交叉编译的介绍。
我将从基础开始讲起,然后慢慢地深入到更多复杂的问题区域。

这篇文章是有关ActionScript到JavaScript的交叉编译系列文章中的一篇。为了说明一些ActionScript到JavaScript交叉编译的基础性问题,我想从简单的JavaScript语句开始讲起:

alert(0.2+0.1);

Chrome,Safari,Firefox,和IE8得到的结果都是0.30000000000000004,这可能让你感到很惊讶。不好的消息是结果是错误的,好消息是在所有的主流浏览器当中,我们都得到了一个相同的错误的结果。

这个令人惊讶的结果应该凸显出了一些问题:
这是一个bug吗?
如果是的话,那怎么那些浏览器厂商不在浏览器中进行修正呢?
如果不是的话,那么这个是否在JavaScript语言规范ECMA-262中有定义呢?

但是更糟糕的是:
如果ActionScript将“0.2+0.1”的结果正确返回0.3又会是怎样呢?

如果这样的话,我们将会陷入很糟糕的困境中。让我带你度过这个难关…

交叉编译器在将语句翻译成JavaScript的时候应该保持语句“0.2+0.1”不变吗?
当在浏览器中运行的时候得到的结果不是0.3。并且产生的JavaScript代码的内部一致性可能会受损,因为我们的ActionScript代码假定0.2+0.1将会得到0.3。

在JavaScript里面交叉编译器应该加入代码来保证得到的结果是0.3吗?
怎样?比如像这样:

alert((0.2*10+0.1*10)/10);

这听起来不像是一个好的想法。像上面的代码做任何修改,或者加入Math.round()等将会带来许多意想不到的副作用。

交叉编译器应该试着在编译阶段检查常量值,然后来完全地避免这个问题?
像这样:

alert(0.3);

注意了,我将带你去搞定这个问题。上面的代码不是一个好的解决方案,这里有几个原因。一个是:虽然在这个例子中,常量合并可能避免了这个问题,但是你可以简单地设计出一个例子,在这个例子中,交叉编译器不能在编译阶段检查常量值:

function??addNumbers ( left??, right )
{
return left + right;
}
alert(??addNumbers??(??0.2??+??0.1??)??);

为什么加入0.3不是一个好的主意,主要原因是这不是必须的。其实,在这个例子中常量合并将会带来意想不到的结果。假如你认为这是一番胡言论语的话,继续读下去…
快乐的结局
所以,对于将 “0.2+0.1”从ActionScript交叉编译到JavaScript,我们有没有疑问呢?ActionScript返回的结果是什么呢?

0.30000000000000004

我很幸运!这是我们都应该知道的:我们在ActionScript和JavaScript中得到了相同的错误的结果。换句话说就是交叉编译器应该没有改变想“0.2+0.1”这样的数值型语句。

收场白:
交叉编译器最重要的作用是保留输入代码在目标代码中的内部一致性。ActionScript代码当被编译成SWF并且在FlashPlayer中运行,与代码被交叉编译成JavaScript并且在浏览器中运行,这两种情况理论上应该得到相同的结果。不管结果是对还是错,没有关系。源代码和目标代码之间在运行时的结果是否不同才有关系。

最后:
1.一个交叉编译器需要保留输入代码在生成代码中的内部一致性;
2.一个交叉编译器需要去掉歧义性,这种歧义可能导致生成的代码产生不同的运行时行为;
3.即使一门像JavaScript这样的很规范化的语言也可能有不规范的地方;
4.有的时候幸运比聪明更好。
顺便说下,假如你对JavaScript中像“0.2+0.1”这样的奇特的东西感兴趣的话,我建议你看看Crockford on JavaScript?的讨论和阅读wtfjs.com上面的东西。


编者:FalconJS (一个转换AS3为JavaSript的编译器的项目)的开发者发表了一系列博客,讲述了对类,继承,接口,包和命名空间这几个方面,ActionScript语言特性以及怎样在JavaScript里面对它们进行模拟,ActionScript到JavaScript的交叉编译等。对于希望学习JavaScript的AS3开发者们很有帮助。这是这个系列的第二篇。

如果你准备利用一个工具或SDK,想通过它将已有的由ActionScript编写的Flex或Flash工程转换为JavaScript形式的HTML网页,那么你的这个工具就必须支持两种特性:
1.语言特性。
2.运行时特性。

语言特性无非是诸如“类”、“继承”等等的特征,运行时特性则指Flash运行时以及像比Sprite和Element浏览器特性。

AS到JS的交叉转换编译一文中,我将会集中在AS和JS的语言差异上。随后我还会专门写博客讨论运行时特性。

理想状态下,你的交叉编译器应该把源语种里的每一个每一个符号转化为目标语种里等价的符号。最主要的目标是在生成的代码中保持原代码内部的一致性和相容性。幸运的是,AS和JS有很多重叠的语法特性。但不可避免,AS中的某些语言特性与JS不同,或者干脆在JS里就找不到。在语法层面上你可能注意到,AS添加有这么些关键字:

  1. as, class, const, extends, implements, import, internal,
  2. is, native, package, private, protected, public, super,
  3. use, void, get, set, namespace, include, dynamic, final,
  4. override, static.

AS和JS的语言差异也可以划分为缺失特性和所具特性,这两点只是貌似、实际很不一样。

JS中缺失的语言特性:
*类,继承,接口和构造器函数。
*getter/setter
*默认参数
*包,命名空间
*变量类型:const和static
*数据类型:int,uint,Vector.<T>
*for…each
*E4X(ECMA-357)
*弱引用(可以查看Dictionary,EventDispatcher.addEventListener

语言特性的差异:
*方法中”this”的含义差异
*核心API:Error,Data。

后续的文章里我将会一一展示上面列表中的内容,并且会介绍这些特性的向JS语言特性的等价转换(当然有时候只能是尽可能等价)。

在此声明,我并不是说我提倡的转换就是最好的方案。我所讲的转换只是从原生AS代码中得到等效JS方案中的一种而已。还有了,还要强调的是我的代码示例不保证跟FalconJS生成的一致。大家不要觉得我给出的范例都是FalconJS方案。

最后,本站点文章不一定代表Adobe官方的立场,观点和意见。我的代码示例都是未经加工整理就拿给大家看了,主要起着演示目的。
现在一切都准备好了,我们接着开始类,继承和接口这些更有趣的内容吧!



编者:FalconJS (一个转换AS3为JavaSript的编译器的项目)的开发者发表了一系列博客,讲述了对类,继承,接口,包和命名空间这几个方面,ActionScript语言特性以及怎样在JavaScript里面对它们进行模拟,ActionScript到JavaScript的交叉编译等。对于希望学习JavaScript的AS3开发者们很有帮助。这是这个系列的第三篇。

这是AS到JS交叉编译的系列博客,本文讨论下面列出的这些AS语言特性,以及如何在运行表现行为不变的前提下将它们转换为对等的JS代码。
*类和继承
*方法和成员
*构造器函数
*getter/setter
*默认参数
*接口
*包
*命名空间

类和继承
类的继承是JS不具备的语言特性,我们需要靠模仿实现。

// ActionScript:
public class Fruit
{
}

public class Orange extends Fruit
{
}

幸运的是,几乎每个成型的JS框架都拥有函数来模仿类和继承。简单起见,我在这里使用Google的Closure Library框架下的base.js。在这个例子里,Orange和Fruit是两个类,我们是通过空函数来代表的。通过使用Google的base.js里的goog.inherits()函数,我们实现了Orange从Fruit的继承。

// JavaScript:
var Fruit = function() {};
var Orange = function() {};
goog.inherits(Orange, Fruit);

方法和成员
类可能实现静态方法和静态成员,也可能是实例化的方法和成员。这些可以被轻松的映射到JS的表达式中。

// ActionScript:
public class MyClass
{
    public const instanceName : String = "MyClass Instance";
    public function getInstanceName() : String { return instanceName; }

    public static const staticName : String = "MyClass";
    public static function getClassName() : String { return staticName; }
}

产生的JS代码可以是这样:

// JavaScript:
var MyClass = function() {};
MyClass.prototype.instanceName = "MyClass";
MyClass.prototype.getInstanceName = function() { return this.instanceName; };
MyClass.staticName = "MyClass";
MyClass.getClassName = function() { return MyClass.staticName; };

在JS中,你不得不经常使用”this”来获取到实例成员(也就是”this.instanceName”这句)。你可能注意到从”staticName”到”Myclass.staticName”的转换显得奇怪。交叉编译器需要将names分析成对应类型:实例成员(“this.instanceName”),静态成员(MyClass.staticName),或者是变量名。

构造器函数
在AS中,构造器函数就是一个跟类名一致、不具备返回值的方法

 // ActionScript:
public class MyClass
{
    public function MyClass()
    {
        trace("MyClass ctor");
    }
}

在JS中我们可以重用刚才模拟类的函数来模拟构造器函数

  // JavaScript:
var MyClass = function()
    {
        trace("MyClass ctor");
    };

有关构造器函数还有很多话题,像比在继承了另一个实现了构造器函数的类时,如何调用父类以及注入缺省情况下调用super()。这些将在后续教程详细探讨。

Getter/Setter
AS支持getter和setter函数

 // ActionScript:

     public class MyClass
     {
         private var m_name : String = "MyClass";
         public function get name() : String
         {
             return m_name;
         }
         public function set name(value:String) : void
         {
             m_name = value;
         }
     }

     var myClass : MyClass = new MyClass();
     myClass.name = "MyClass";
     trace( myClass.name );
  似乎JS最终会支持[url=http://ejohn.org/blog/javascript-getters-and-setters/]getters和setters[/url]。早期版本的[url=http://blogs.adobe.com/bparadie/2011/11/19/what-is-falconjs/]FalconJS[/url]确实使用了ECMAScript5规范的getters和setters。它确实有效,但我留意到当使用"原生的"JS getters和setters时候,性能会出现下降。如果性能表现仍然是瓶颈,你可能需要自己实现getters/setters,以使得浏览器更为流畅。生成的JS可能是这样的:
      // JavaScript:
    var MyClass = function() {};
    MyClass.prototype.m_name = "MyClass";
    MyClass.prototype.get_name = function() { return m_name; }
    MyClass.prototype.set_name = function(value) { m_name = value; }

    var myClass = new MyClass();
    myClass.set_name("MyClass");
    trace( myClass.get_name() );

在编译时将myClass.name转换为myClass.get_name()和myClass.set_name()并非是可有可无的工作,它要求你做大量的静态分析。还有一些情形下,几乎不可能去分析一个变量的类型。幸运的是,绝大多数这样的情况都是由无类型代码引起的,这可以通过在源代码中引入类型而轻松的定位。

默认参数
AS支持默认参数,JS不能。

  // ActionScript:
     public class MyClass
     {
         public function helloWorld( s1 : String = "Hello",
                                     s2 : String = "World!" ) : String

         {
             return s1 + ", " + s2;
         }
     }

我们可以这样在JS里模仿实现默认参数。

// JavaScript:
var MyClass = function() {};
MyClass.prototype.helloWorld = function(_default_s1, _default_s2)
{
     var s1 = (typeof(_default_s1) != "undefined") ? _default_s1 : "Hello";
     var s2 = (typeof(_default_s2) != "undefined") ? _default_s2 : "World!";
     return s1 + ", " + s2;
}

接口
JS不支持接口,并且当它们出现在含有”is”或”instanceof”语句的右侧时,会引发问题。

 // ActionScript:

    public interface IBall
    {
        function roll() : void;
    }

    public class Ball implements IBall
    {
        public function roll() : void
        {
        }
    }

    var ball : Ball = new Ball();
    trace( ball is IBall ); // "true"

将AS接口转换为JS时候,最大的问题就是一个类可能实现多个接口,而且它们的父类也可能实现了多个接口。这有点儿像是噩梦了,解决方案可能只有通过追加类信息,将类的名字映射到接口列表名里。每个接口都可以通过一个Object代表,而”is”和”instanceof”操作符则可以被虚拟化,通过调用一些有具体实用功能的函数,就像比

as3.instanceOf();
      // JavaScript:
    var IBall = {"_NAME": "IBall"};
    var Ball = function() {};
    Ball._NAME = "Ball";
    Ball.prototype._CLASS = Ball; // the instance points to the class
    Ball.prototype.roll = function() {};
    as3.implements( Ball, [IBall]); // registers IBall for Ball.

    trace( as3.instanceOf( ball, IBall ) ); // hopefully "true"

为了判定”ball”是否实现了IBall以确定一个实例是否属IBall类型,我们需要在实例(也就是_CLASS)中存储Ball这个类。每个类都需要携带一个名字(也就是_NAME),并且需要通过as3.implements()函数给每个类所实现的接口进行注册。通过这些补充我们应该能够实现as3.instanceOf()函数。如下:

 // JavaScript:
    as3.instanceOf = function(instance, classOrInterface)
    {
        if( instance instanceof classOrInterface )
            return true;

        var interfaces = as3.interfaceInfo[instance._CLASS._NAME];
        for( var interface in interfaces )
        {
            if( interface == classOrInterface._NAME )
                return true;
        }
        return false;
    }
(注意这个版本的as3.instanceOf函数并未遍历父级链)

[size=5]包[/size]
AS3鼓励将类嵌入包中:
    // ActionScript:

    package flash.display
    {
        public class Sprite
        {
        }
    }

在JS里没有包,但我们可以这样做:

// JavaScript:
var flash = {};
flash.display = {};
flash.display.Sprite = function() {};

[size=5]命名空间:[/size]

命名空间相当复杂。AS有一些内建的命名空间像比”public”,”private”,”proteted”,”internal”。
你还可以使用自己的命名空间:

  // ActionScript:
public namespace mx_internal = "http://www.adobe.com/2006/flex/mx/internal";

在JS里每个属性都是公开的,有一些隐藏属性和方法的技巧,你可以用它们来模拟”private”。
但此时此刻,我们权且认为所有命名空间都可以被多多少少的忽略并且当做”public来对待。


编者:FalconJS (一个转换AS3为JavaSript的编译器的项目)的开发者发表了一系列博客,讲述了对类,继承,接口,包和命名空间这几个方面,ActionScript语言特性以及怎样在JavaScript里面对它们进行模拟,ActionScript到JavaScript的交叉编译等。对于希望学习JavaScript的AS3开发者们很有帮助。这是这个系列的第四篇。

这是AS到JS交叉编译系列文章之一,这篇文章我们将继续关于ActionScript语言特性以及怎样在JavaScript里面对它们进行模拟的探讨。这部分你将学到下面的知识:
常量和类型
int 和 uint
vectors
for…each
E4X

常量和类型
ActionScript鼓励使用类型和常量。

Const??num:Number =1.0;

对成员声明而言,“const”关键字可以被忽略,或者也可以用“var”来替代。在JavaScript中没有了类型。

Var??num=1.0;

int 和 uint?
在JavaScript中只有一种Number数据类型,然而ActionScript中支持Number,int和uint。这里很有问题,因为int和uint代表32为整数,相比起Number表示的数据范围,int 和uint表示的数据范围小很多。

int .MAX_VALUE =2147483647;
int.MIN_value=-2147483648;
uint.MAX_VALUE=4294967295;
uint.MIN_VALUE=0;

为什么这里有问题?因为int 和uint在它们的数据范围的边界进行了循环转换。

//ActionScript:
var num : uint = 4294967295;??// = uint.MAX_VALUE
num++; ?// becomes 0

这是个不好的消息。这意味着我们不能讲uint和int转换为Numbers.

// JavaScript:
var num = 4294967295;??// = uint.MAX_VALUE
num++; ? // becomes 4294967296.0, because Number has a wider range

为了正确地将uint 和int转变成JavaScript,除了加入实例函数调用来模拟int 和uint的循环转换行为,我们别无选择。

// JavaScript:
var num = 4294967295;?// = uint.MAX_VALUE
num =?as3.addToUint(num, 1);? ?// calling utility?function?that emulates uint.

隐藏在这后面的含义将是毁灭性的。你可以想象,加入的实例函数将会对涉及到int 和uint的每一项数值操作进行调用,这将会使JavaScript代码运行变慢。为了避免那样的事情发生,我们可以灵活掌握规则。下面是三种思路:

1.“循环转换是bugs”-不用加入实例函数调用并且接受ActionScript和JavaScript之间的不同之处。
2.“使用Number来代替”-放弃对int和uint的支持并且如果客户代码使用了uint或者int,就抛出一个语法错误。
3.“这只是在debug的时候比较糟糕”-利用“dart 规则”以及只在debug模式下加入函数调用。在release模式下我们将使用原始的表达式。

除了潜在的拥有诸如实例函数调用功能来进行数值操作,对于像MAX_VALUE和instanceof这样的东西,我们可能也应该创建伪造的int和uint级别。

// ActionScript:
var num : uint = uint.MAX_VALUE;
trace( num instanceof uint );

假如你使用“uint”和”int”作为标识符的话,谷歌的最优化编译器会关闭提示。这就是使用UintClass和IntClass来替代它们可能会更好的原因。

// JavaScript:
var UintClass = { MAX_VALUE: 4294967295 };
trace( as3.instanceOf(num, UintClass) );

Vectors
ActionScript Vectors加入到ActionScript中的时间不算长。我喜欢他们:

// ActionScript:
var vec : Vector.<int> = new Vector.<int>();
var myFilter = function(item:int, index:int, vector:Vector.<int>):Boolean {};
var result:Vector.<int> = vec.filter(myFilter);

由于ActionScript的Vector?API是Array API的拓展集,我们可以将Vectors转成Arrays,并且对于那些Arrays’ API没有涉及到的部分,我们可以加入实例函数调用。

// JavaScript:
var vec = [];
var myFilter = function(item, index, vector) {};
var result = as3.vectorFilter(vec, myFilter);

for…each
在ActionScript中for…each和for…in非常相似:
FOR EACH能够访问数组的元素。
FOR IN可以访问数组的索引。
这里有一个ActionScript的例子:

// ActionScript:
const haystack : Object = {a:1, b:2, c:3};
for each( var needle: uint in haystack )
{
trace( needle );? ?// 1, 2, 3
}

幸运的是,for…each语句可以很容易地转换成for…in语句;

// JavaScript:
var haystack = {a:1, b:2, c:3};
for(var needle1 in haystackObj)
{
needle = haystack[needle].toString();
trace(needle);
}

E4X
根据*的定义,“ECMAScript for?XML(E4X)是一门拓展了ECMAScript(包含ActionScript,JavaScript和JScript)的编程语言,他增加了对XML的内在支持。这个特征看起来很受欢迎。例如:Open Source Media Framework(OSMF),away3D以及papervision3D都使用E4X。但是事实是主流浏览器中除了Firefox外,其它的都不支持E4X,并且似乎不见得他们在将来的某个时候会支持。甚至Mozilla似乎也不再使用E4X。在他们的网站上有这样一条警告:
警告:除了安全问题之外,Mozilla不再积极地维护E4X。不要用它来做任何新的工作。

E4X到JavaScript的交叉编译可以做到,但是可能这不值得这么折腾,你必须对每一个E4X表达式加入合适的实力函数调用。我建议在将ActonScript交叉编译到JavaScript的时候,放弃对E4X的支持。也就是说,如果客户代码使用了E4X,我们的交叉编译器应该报告清晰的错误信息。