skip to main |skip to sidebar

2008-06-22

[AS3]详解E4X的变量展开(3)——效率与实用性

从上一篇文章可以看出使用 E4X 变量展开要慢于 new String。因为 E4X 是先把字符串转换为 XML(XMLList) 再转换为字符串。在解析字符串时开销很大。下面是测试的结果:

方式
第1次
第2次
第3次
第4次
第5次
平均
E4X 变量展开
1008
1490
959
1037
959
1091
new String
135
110
125
111
117
120

(E4X 变量展开的代码为:<>abc{1+2}def</>.toString(),new String 的代码为:"abc" + (1+2) + "def";循环次数为10000次;单位:ms。)

由此可见 E4X 变量展开的效率要差一些,但不在大量的循环里的时候差别不是很明显。因为这里的差别是循环 10000 次的结果,执行一次的差别仅仅为 0.097(ms),是可以忽略不计的。因此 E4X 变量展开还是很有用的。

比如说以下代码(预载入文本):

E4X 变量展开的格式:

e4xStr(<>Loading... {loaded}K/{total}K ({loaded/total*100}%)</>)
//e4xStr 为
function e4xStr(e4x:XMLList):String {
  return e4x.toString();
}

new String 的格式:

"Loading... " + loaded + "K/" + total + "K (" + (loaded/total*100) + "%)"

由此可见 E4X 变量展开的可读性要大大高于new String 的格式。因为预载入文本一般为每一帧执行一次,所以说一般不会遇到效率的瓶颈。

2008-06-15

[AS3]详解E4X的变量展开(2)——内部实现

[AS3]E4X的变量展开详解(1)——基础,下面来研究E4X变量展开的内部实现。

源文件:

var a = <a>abc{123}def</a>;

使用 abcdump 反编译后的结果:

 0        getlocal0
1 pushscope
2 findpropstrict XML
4 getproperty XML
6 pushstring "<a>abc"
8 pushbyte 123
10 esc_xelem
11 add
12 pushstring "def</a>"
14 add
15 construct (1)
17 getglobalscope
18 swap
19 setslot 1
21 returnvoid

new String() 做比较

源文件:

var a = new String("a")

使用 abcdump 反编译后的结果:

 0        getlocal0
1 pushscope
2 findpropstrict String
4 pushstring "a"
6 constructprop String (1)
9 getglobalscope
10 swap
11 setslot 1
13 returnvoid

会发现 E4X 与 new String() 很相似。

先来看压栈的部分

new String() 的为:

4        pushstring          "a"
6 constructprop String (1)

new String() 是先把 "a" 压入堆栈,再调用构造函数。

而 E4X 的为:

 6        pushstring          "<a>abc"
8 pushbyte 123
10 esc_xelem
11 add
12 pushstring "def</a>"
14 add
15 construct (1)

有点复杂,过程为:

1.把 "<a>abc" 压入堆栈(第6行)

"<a>abc"
XML

2.把 123 压入堆栈(第8行)

123
"<a>abc"
XML

3.把 栈顶(这里为 123)转换为 String。(第10行) esc_xelem 相当于 toXMLString(123) *注意 toXMLString 为内部函数,无法调用。

"123"
"<a>abc"
XML

4.把堆栈的栈顶的两项相加。(第11行)

"<a>abc123"
XML

5.把 "def</a>" 压入堆栈(第12行)

"def</a>"
"<a>abc123"
XML

6.把堆栈的栈顶的两项相加。(第14行)

"<a>abc123def</a>"
XML

7.调用构造函数。(第15行)即返回 new XML("<a>abc123def</a>")

总结

<a>abc{123}def</a>;

就相当于

new XML("<a>abc" + toXMLString(123) + "def</a>");

最后,关于AVM2的格式可以参照AVM2 Instructions,不明白的语句可以去查一查。

未完待续,下一篇详解效率与实用性

2008-06-14

[AS3]E4X的变量展开详解(1)——基础

在 E4X 里可以使用 {} 来进行变量展开。比如下以下代码:

var foo:String = "123";
trace(<>abc{foo}def</>.toString());// -> abc123def

{} 里面可以为在执行期才知道的变量:

var foo:String = Math.random().toSTring();
trace(<>abc{foo}def</>.toString());

{} 还可以使用表达式(运算符、调用函数,etc.):

var foo:String = "123";
trace(<>{foo + foo}</>.toString());// -> 123123
//比较复杂一点的
trace(<>{foo += "456", foo+"789"}</>.toString());// -> 123456789
//调用函数
trace(<>{foo.substr(1)}</>.toString());// -> 23

{} 里无法使用语句,但可以使用匿名函数来代替:

var foo:String = "123";
trace(<>{(function(a){return a + a})(foo)}</>.toString());// -> 123123

使用 E4X 会减少一些代码,方便书写,但注意 <></> 的类型为 XMLList

最后给一个方便的函数:

public function e4xstr(e4x:XMLList):String {
  return e4x.toString();
}
//用法
var a:int = 3;
var b:int = 5;
trace(e4xstr(<>a+b={a+b}, a*b={a*b}</>)); // -> a+b=8, a*b=15

未完待续,下一篇详解 E4X 的内部实现

2008-06-07

[AS3]逻辑和(&&)与逻辑或(||)的妙用

使用 &&|| 可以简化代码,提高可读性。比如以下代码:

var foo:Boolean = true;
var bar:Object = {};
trace(foo && bar);  // -> [object Object]
trace(foo || bar);  // -> true

&&|| 的结果是由运算符的左边的项目(这里是 foo )来决定的。简单地说,规则为:

A && B : 如果 Boolean(A) 为 false 则返回 A,否则返回 B。
A || B : 如果 Boolean(A) 为 true 则返回 A,否则返回 B。

所以实际上并没有进行真正数学意义上的逻辑运算,返回值也不一定是 Boolean 值。

Boolean(A) 的转换规则如下:

Undefiend  : false
Null   : false
Boolean  : 与转换前相同
Number   : 0 或 NaN 为 false 替其他的为 true
String   : 空串为 false  其他的为 true
Object   : true

使用逻辑运算符简化代码

使用逻辑运算符可以简化简单的逻辑判断(if)。

//1. 使用 if
if (foo) {
  doSomething();
}
if (!bar) {
  doSomething2();
}

//2. 使用逻辑运算符
foo && doSomething();
bar || doSomething2();

多重 if 的写法:

//1. 使用 if
if (foo) {
  if (bar) {
    sayHello();
  }
}
 
//2. 使用 if 与逻辑运算符
if (foo && bar) {
  sayHello();
}

//3. 只使用逻辑运算符
foo && bar && sayHello();     

虽然 else 也可以实现,但代码有可能变得难一閲读。

&&= 与 ||=

在需要代入结果时,使用 &&=||= 更方便。

//1. 使用 if
if (foo) {
  foo =  "<" + foo + "/>";
}

//2. 使用逻辑运算
foo &&= "<" + foo + "/>";

2008-04-30

我的 Blog 的 Google PR 上升为 7!

在 2008-4 的 Google PR 的更新里我的 Blog 已经上升为7了。

在这里感谢大家对我的支持。

再请大家多多关注一下我的另一个 Blog —— ActionScript Snippets - 一个专门收集 AS 代码的 Blog,它的 PR 也上升为了3。

2008-04-27

[AS3]ActionScript 中数组的访问的 BUG

重要度:(3/5) 这个问题看似简单,但 debug 时却难以发现。

数组访问时如果下标使用了函数并且使用了 +=-=*= 等运算符时需要特别地注意,函数会被调用两次!

比如以下代码:

var i:int = 0;
var foo = function():int {
trace("foo");
return ++i;
}
var arr:Array = [1,2,3];
arr[foo()] += 10;
trace(arr);

将会输出:

foo;
foo;
1,13,3

而正确的输出为:

foo;
1,12,3

这是因为在编译时编译器只做了一下简单的操作:

(a += b) --> (a) = (a) + (b)

以至于将 arr[foo()] += 10; 编译成 arr[foo()] = arr[foo()] + 10;

2008-04-06

[AS3]mxmlc 编译器的 BUG

PS:因为最近很忙,好久没有更新 Blog 了。

最近发现了一个 mxmlc 编译器的 BUG,当含有 -(true ? 1 : 0) 的代码编译会出错。

比如以下代码:

package {
import flash.display.Sprite;
public class TestBUG extends Sprite {

public function TestBUG() {
trace('Test');
var foo:Number = -(true ? 1 : 0);
}
}
}

使用 mxmlc 编译时会弹出错误:

Error: null
java.lang.NullPointerException
  at macromedia.asc.semantics.ConstantEvaluator.evaluate(ConstantEvaluator.java:1168)
  at macromedia.asc.parser.UnaryExpressionNode.evaluate(UnaryExpressionNode.java:33)
  at macromedia.asc.semantics.ConstantEvaluator.evaluate(ConstantEvaluator.java:1805)
......

但把 -(true ? 1 : 0)- 号去掉后编译却可以正常进行,真奇怪!

影响的版本:

  • 2.0
  • 2.0.1
  • 3.0.0

2008-03-20

[Flash Player]2008年4月的 Flash Player 的安全策略的变化

在 Adobe 上公布了将在4月份改变 FLash Player 9 的安全策略。(Preparing for the Flash Player 9 April 2008 Security Update

这次的改变纯粹是为了安全,没有增加任何的新特性。但是默认的策略发生了一些改变,有可能会导致现有的一些程序无法正确地工作。

javascript 协议的改变

在以前,loadMovie() 里可以使用 javascript:... 的形式,但新的版本里只在 getURL()navigateToURL()ExternalInterface 里支持 JavaScript。

allowScriptAcces的变化

allowScriptAccess 是指定从 swf 访问 HTML 里的 Script 的方式的属性。以前的 Flash player 对于版本7以下的 swf 的 allowScriptAccess 属性的默认值为 always,版本8以上的默认值为 sameDomain

但新的版本对所有的版本的 swf 的默认值都将变为 sameDomain。版本7以下的 swf 必须明确地指定 allowScriptAccess = always

传送 HTTP Header 时的变化

使用 XML.addRequestHeader()LoadVars.addRequestHeader()URLRequest.requestHeaders 可以添加 HTTP 请求的 Header。在新的版本里向非同域名的服务器发送请求并添加 Header 时先必须要取得服务器的的许可。

具体的是需要在服务器上的 crossdomain.xml 添加添加以下项目:

<allow-http-request-headers-from domain="www.example.com" headers="HeaderName"/>

Socket 连接服务器时必须使用安全策略文件

XMLSocket 与 Socket 连接服务器的安全策略将会更严。新的版本在连接同域名时也需要使用安全策略文件,并且请求安全策略文件的端口固定为843。

2008-03-16

[AS3] ActionScript 3 的代码规范

Adobe 推出了AS3 的代码的书写规范。详情参见:Coding Conventions

它推出的代码规范并不意味着开发者必须严格地遵行,仅仅是建议罢了(开发 Flex SDK 的除外)

以下为我推荐的一些内容:

数组的声明格式

var a:Array /* of String */ = [];
var a:Array /* of int */ = [ 1, 2, 3 ];
function f(a:Array /* of Number */):Array /* of Object */  {
  //...
}

不要写成

var a:Array = [];
var a:Array = new Array();
var a:Array = new Array(1, 2, 3);
function f(a:Array):Array {
  //...
|

Object 的声明格式

var o:Object = {};
var o:Object = { a: 1, b: 2, c: 3 };

不要写成

var o:Object = new Object();
var o:Object = new Object();
o.a = 1;
o.b = 2;
o.c = 3

比较

if (n == 3) // "如果 n 是 3"

不要写成

if (3 == n) // "如果 3 是 n"

2008-02-17

永远的 SoundComplete 事件

这已经不是什么新闻了,只是关注度好像不是很高,但我认为很有必要说几句。

自从 Flash Player 升级为 9.0.115.0 后 Sound.soundComplete 事件就消失了!没有任何征兆,一夜之间就从这个世界消失得无影无踪。

不要小看这个事件,没有它音频控制几乎不可能。比如以前一直使用 Sound.soundComplete 事件来实现音频的连续回放等各种特效,现在一切变为了南柯一梦。特别是 popforge 项目,一个 使用 AS3 来创作与修改音频的先端开发的新兴项目成为了最大的受害者之一,它有可能因此而从此玉损了!

大家署名来抗议吧The silent death of onSoundComplete event - a petition

2008-02-07

祝大家新春快乐

今天是初一,祝大家鼠年快快乐乐。也祝我的网站日月兴隆。

[AS3]让构造函数不执行 super()

在构造函数里一般 super() 必须执行。如果不写,在编译时会在构造函数的最上面添加 super()。但有时又需要不执行。比如以下代码:

class AbstractClass {
  public function AbstractClass() {
    throw new Error("Abstract class");
  }
}
class SubClass extends AbstractClass {
  public function SubClass() {
    trace("sub class");
  }
}

SubClass 对象会因为超类弹出错误而创建失败。但如果让 super() 不执行就可以了。

class SubClass extends AbstractClass {
  public function SubClass() {
    if(0) { //这里必须为返回 false 的表达式。但不能为 "false"(false 会被编译器忽视)。
      super();
    }
    trace("sub class");
  }
}

测试代码:

package {
  import flash.display.*;
  public class Test extends Sprite {
    public function Test() {
      new AbstractClass(); // -> Super CLass
      new SubClass1(); // -> Super CLass / Sub CLass1 start / Sub CLass1 end
      new SubClass2(); // -> Sub CLass2 start / Sub CLass2 end
    }
  }
}
class AbstractClass {
  public function AbstractClass() {
    trace("Super CLass");
  }
}
class SubClass1 {
  public function AbstractClass() {
    trace("Sub CLass1 start");
    super();
    trace("Sub CLass1 end");
  }
}
class SubClass2 {
  public function AbstractClass() {
    trace("Sub CLass2 start");
    if(0) {
      super();
    }
    trace("Sub CLass2 end");
  }
}

2008-02-03

[Flex]把 Sprite 添加到 Canvas 里的方

Flex FrameworkCanvas 是无法添加 Sprite 等非 UI 组件的 DisplayObject的。但有变通的办法。

1.使用 Adaptor 模式

Sprite 添加到 UIComponent,再把 UIComponent 添加到 Canvas

代码:

var mySprite:Sprite = new Sprite();
mySprite.graphics.beginFill(0xFFCC00);
mySprite.graphics.drawCircle( 40, 40, 40 );
var UIRef:UIComponent = new UIComponent();
myUI.addChild(UIRef);
UIRef.addChild(mySprite);

2.使用 rawChildren

使用 Canvas.rawChildren

代码:

var mySprite:Sprite = new Sprite();
mySprite.graphics.beginFill(0xFFCC00);
mySprite.graphics.drawCircle( 40, 40, 40 );
myUI.rawChildren.addChild(mySprite);

2008-01-30

ActionScript Snippets 加入了 RXNA

我的专门收集代码的博客 ActionScript Snippets 加入 MXNA 了。

大家可以常去看看。

2008-01-26

POP3 协议很危险!

大家注意,POP3 协议(非 POP3s)十分危险!

如果使用未加密的 POP3 协议来收邮件,你的用户名与密码就用可能被盗

以下是我使用 Wireshark 接下的包:

可以看到,用户名与密码写得一清二楚。即使一个刚入门的骇客也知道该怎么解读。

解决办法:

  • 可以使用安全的 POP3s 协议。POP3s 协议相当于加了密的 POP3,这样用户名与密码就难以解读了。但并不是所有的邮件提供商都支持 POP3s 协议。
  • 经常换密码,这样即使被盗,也是暂时的,把损失降到最小。
  • 避免用邮件发送重要的信息。

2008-01-20

[AS3]小心使用连续赋值

连续赋值即为 a = b = 1 的形式的赋值。这样写有时候会很方便,但有一个陷阱!

比如以下代码:

var a:Array, b:String;
a = b = null;

会报类型错误。因为在内部是编译成以下形式的。

var a:Array;
var b:String;
b = null;
a = b;

理所当然会报错了。以后可要注意。

2008-01-14

[AS3]for 循环数组的效率测试

循环 ("abcdefghijklmnopqrstuvwxyz").split("") 这个数组 100000 次的结果:(单位:ms)

No.for 循环for..in 循环for each 循环
1
17  
8213  
2165  
2
19  
8126  
2188  
3
18  
8338  
2128  
4
18  
7100  
2409  
5
17  
8054  
2945  
平均
17.8
7900.2
2367.0
偏差(越小越稳定)
0.748
443.326
305.225

测试程序:

源码:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical"
 scriptTimeLimit="10"
 width="520" height="490">
 <mx:Script>
 <![CDATA[
 
 private static const FOR:String = "for 循环";
 private static const FOR_IN:String = "for..in 循环";
 private static const FOR_EACH:String = "for each 循环"
 private static var LOOP_SRC:Array = ("abcdefghijklmnopqrstuvwxyz").split("");
 
 private function test(testCase:String):void {
  var loopCount:int = int(count_ns.value);
  var start:int, now:int;
  var i:int;
  output("@" + testCase);
  try {
   switch (testCase) {
    case FOR:
     start = getTimer();
     for (i = 0; i < loopCount; i++) {
      //var length:int = LOOP_SRC.length;
      for(var j:int; j<LOOP_SRC.length; j++) {
       LOOP_SRC[j].toUpperCase();
      }
     }
     now = getTimer();
     break;
    case FOR_IN:
     start = getTimer();
     for (i = 0; i < loopCount; i++) {
      for(var j2:String in LOOP_SRC) {
       LOOP_SRC[j2].toUpperCase();
      }
     }
     now = getTimer();
     break;
    case FOR_EACH:
     start = getTimer();
     for (i = 0; i < loopCount; i++) {
      for each(var v:String in LOOP_SRC) {
       v.toUpperCase();
      }
     }
     now = getTimer();
     break;
   }
  }
  catch (e:Error) {
   now = getTimer();
   output("  " + i + "次时弹出错误: " + e);
  }
  var time:int = now - start;
  output("  经过时间(ms): " + nf.format(time));
  output("  1秒中的循环次数: " + nf.format(i * 1000 / time));
  output("----------------------------------------");
 }

 private function output(msg:String):void {
  output_ta.text += msg + "\n";
 }
 ]]>
 </mx:Script>
 
 <mx:NumberFormatter id="nf" precision="0" />
 <mx:Panel title="循环数组的压力测试"
  paddingTop="8" paddingRight="8" paddingBottom="8" paddingLeft="8">
  <mx:HBox>
   <mx:Label text="循环回数:" />
   <mx:NumericStepper id="count_ns" value="100000" maximum="10000000" />
  </mx:HBox>
  <mx:HBox>
   <mx:Button label="{FOR}" click="test(FOR)" />
   <mx:Button label="{FOR_IN}" click="test(FOR_IN)" />
   <mx:Button label="{FOR_EACH}" click="test(FOR_EACH)" />
  </mx:HBox>
  <mx:TextArea id="output_ta" width="400" height="300" />
  <mx:Label color="red" text="*Application.scriptTimeLimit设为了10秒。" />
 </mx:Panel>
</mx:Application>

2008-01-13

[AS3]trace() 与 logging 的压力测试

trace() 其实很慢!以后再发布时,一定要注意去掉 trace()mx.logging.Target

源代码:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical"
scriptTimeLimit="5"
width="520" height="490">
<mx:Script>
<![CDATA[
import mx.logging.ILogger;
import mx.logging.Log;
import mx.logging.targets.TraceTarget;

private static const logger:ILogger = Log.getLogger("TraceTest");

private static const NONE:String = "无 trace()";
private static const TRACE:String = "有 trace()";
private static const LOGGER:String = "logger.debug()"

private var tt:TraceTarget;

private function test(testCase:String):void {
var loopCount:int = int(count_ns.value);
var start:int, now:int;
var i:int;
output("@" + testCase);
try {
switch (testCase) {
case NONE:
start = getTimer();
for (i = 0; i < loopCount; i++) {
"abcdefghijklmnopqrstuvwxyz";
}
now = getTimer();
break;
case TRACE:
start = getTimer();
for (i = 0; i < loopCount; i++) {
trace("abcdefghijklmnopqrstuvwxyz");
}
now = getTimer();
break;
case LOGGER:
start = getTimer();
for (i = 0; i < loopCount; i++) {
logger.debug("abcdefghijklmnopqrstuvwxyz");
}
now = getTimer();
break;
}
}
catch (e:Error) {
now = getTimer();
output(" " + i + "次时弹出错误: " + e);
}
var time:int = now - start;
output(" 经过时间(ms): " + nf.format(time));
output(" 1秒中的循环次数: " + nf.format(i * 1000 / time));
output("----------------------------------------");
}

private function updateTraceTarget():void {
if (!tt) {
tt = new TraceTarget();
tt.includeDate = false;
tt.includeTime = false;
tt.includeLevel = true;
tt.includeCategory = true;
}

if (traceTarget_ch.selected) {
Log.addTarget(tt);
} else {
Log.removeTarget(tt);
}
}

private function output(msg:String):void {
output_ta.text += msg + "\n";
}
]]>
</mx:Script>

<mx:NumberFormatter id="nf" precision="0" />
<mx:Panel title="trace()与logger.debug()的压力测试"
paddingTop="8" paddingRight="8" paddingBottom="8" paddingLeft="8">
<mx:HBox>
<mx:Label text="循环回数:" />
<mx:NumericStepper id="count_ns" value="1000000" maximum="10000000" />
</mx:HBox>
<mx:HBox>
<mx:Button label="{NONE}" click="test(NONE)" />
<mx:Button label="{TRACE}" click="test(TRACE)" />
<mx:Button label="{LOGGER}" click="test(LOGGER)" />
<mx:CheckBox id="traceTarget_ch" label="添加TraceTarget" change="updateTraceTarget()"/>
</mx:HBox>
<mx:TextArea id="output_ta" width="400" height="300" />
<mx:Label color="red" text="*Application.scriptTimeLimit设为了5秒。" />
</mx:Panel>
</mx:Application>

2008-01-07

[AS3]pixelDissolve 有 Bug?

最近发现使用 BitmapData.pixelDissolve 溶解像素时,无论 randomSeednumOfPixels 参数是多少,sourceRect 指定的矩形的左上角一定会被溶解!

比如设定为溶解 10 个像素时,左上角 + 剩下的像素中取 10 个会变化。设为溶解 0 个时,只有左上角会变化。

例子: 可以看到左上角很不自然。

例子的代码:

package {
import flash.display.*;
import flash.events.Event;
import flash.geom.*;

[SWF(width="32",height="32")]
public class Test2 extends Sprite{
public function Test2():void {
var bitmap:BitmapData = new BitmapData(32, 32, false,0);
var seed:int = 0;
addChild(new Bitmap(bitmap));
addEventListener(Event.ENTER_FRAME,
function (event:Event):void {
seed = bitmap.pixelDissolve(bitmap, bitmap.rect, new Point(0,0),
seed, 5, Math.random()*0x1000000);
}
);
}
}
}

我用的 Flash Player 的版本是 9.0.115.0。

2008-01-06

[AS3]加载大于 2880px 的图片的方法

Flash Player 在加载大于 2880px 的图片时,大于 2880px 的区域将会被剪掉!2880px 这个限度虽然一般不小,但对某些开发来说,却是一堵又高又大的墙。

但在 AS3.0 里,却是可以办到的!使用 URLStream 加载后,用 Loader.loadBytes 来显示

通过二进制的加载,会忽视外部域名的图片的安全沙箱。也就是说可以随便 draw 了。但注意加载本身是需要 crossdomain.xm了的。

2008-01-04

ActionScript Snippets 开通了

ActionScript Snippets 是一个收集 Advanced Flex Project 在开发时有用的代码、类的博客。

代码均遵循 Apache License 2.0。希望对大家有所帮助。

地址:ActionScript Snippets

2008-01-03

解决 Firefox 播放 Flash 时无法移动 Tab 的问题

使用 Firefox 播放重的 Flash 时有时会出现无法移动、关闭 Tab,像是没有响应了一样。这时只需要新开一个标签,就恢复响应了。