从接触as编程到将之束之高阁,前后将近两个月的时间。回顾这两个月的现学现卖的收获,发现无非是在用另一种语法写c++,面向对象的编程其实大抵如此,抽象模块,定义接口,用什么语言也都差不多。而我也只是象垒砖头一样写了些前人写过,或者至少今后的人也写得出来的一些没什么创意的代码。
估计今后难得有再用as的机会了。为了纪念这与as短暂的一面之交,我打算把这两个月中自己仅有的一份还算有点创造性的产物贡献出来,只希望它能帮助继续与as鏖战的兄弟姐妹们少写一些重复重复重复重复的代码。啊,重用啊,重用……
flash提供的调试不甚好用,我能想象许多兄弟姐妹跟我一样不停地写着trace。面向对象的编程创造出的程序很可能是庞大的,也许有上百的类摆在你面前,每个类又有n多的接口,中间相互的调用错综复杂,运行一旦有问题,定位就是个挺大的麻烦。当我面对这样一种情形的时候,我想,要是所有的调用流程都写到日志里,那有多好。
再有一个麻烦,常写as的人都清楚,flash中的调试环境与flash的flash player插件其实有所差别,有些细节的东西,调试器里和网页里的结果是不一样的,flash调试器还会莫名其妙地崩溃……而trace这破东西,最多只能trace 9999行,而且在浏览器里没法用。很多时候我需要flash上有个文本框来显示trace结果,而这个结果,可能是几个M的文本,有几万甚至十几万行之多。
于是在当时任务很赶的情况下,挤出半天的时间写了这个Debug类。事实证明了这个决定的英明,这让我之后的调试时间缩短了十倍不止。
为了先让大家看看效果,我在这里写一个小的例子,有A,B,C三个类,每个类有两个接口,分别为A1,A2,B1,B2,C1,C2。这些类的调用关系如下,大家只要粗略看看,就知道这个Test.fla的运行过程伴随着几个类的实例相互之间乱七八糟的调用。
// A.as:
class A
{
function A( val )
{
_value = val;
}
function A1( val )
{
trace( val + " * " + _value + " : " + ( val * _value ) );
}
function A2( val )
{
trace( val + " + " + _value + " : " + ( val + _value ) );
}
private var _value;
}
// B.as
class B
{
function B( val )
{
_value = val;
}
function B1( a )
{
if ( _value >= 0 ){
a.A1( _value );
} else {
a.A2( _value );
}
}
function B2( a )
{
if ( _value > 0 ){
a.A1( _value * 2 );
} else {
a.A2( _value * 2 );
}
}
private var _value;
}
// C.as:
class C
{
function C( vala, valb )
{
_valueA = vala;
_valueB = valb;
}
function C1()
{
var a = new A( _valueA );
var b = new B( _valueB );
b.B1( a );
b.B2( a );
}
function C2()
{
var a = new A( _valueA + 200 );
var b = new B( _valueB + 200 );
b.B1( a );
b.B2( a );
}
private var _valueA;
private var _valueB;
}
// Test.fla
var c = new C( 25 , 32 );
c.C1();
c.C2();
c = new C( - 30 , - 26 );
c.C1();
c.C2();
运行结果如下:
32 * 25: 800
64 * 25: 1600
232 * 225: 52200
464 * 225: 104400
-26 + -30: -56
-52 + -30: -82
174 * 170: 29580
348 * 170: 59160
你可能觉得我专门写这么滥的几个类给大家演示。好吧,实际上大规模一点的程序类之间的联系还有比这还麻烦得多的。
这个Debug类的魅力将展示如下:
将每个类(A,B,C)的构造函数加上一行:
// A.as
class A
{
function A( val )
{
Debug.TraceClass( " A " , this , " value: " + val );
_value = val;
}
function A1( val )
{
Debug.xtrace( val + " * " + _value + " : " + ( val * _value ) );
}
function A2( val )
{
Debug.xtrace( val + " + " + _value + " : " + ( val + _value ) );
}
private var _value;
}
// B.as
class B
{
function B( val )
{
Debug.TraceClass( " B " , this , " value: " + val );
_value = val;
}
function B1( a )
{
if ( _value >= 0 ){
a.A1( _value );
} else {
a.A2( _value );
}
}
function B2( a )
{
if ( _value > 0 ){
a.A1( _value * 2 );
} else {
a.A2( _value * 2 );
}
}
private var _value;
}
// C.as
class C
{
function C( vala, valb )
{
Debug.TraceClass( " C " , this , " valueA: " + vala + " valueB: " + valb );
_valueA = vala;
_valueB = valb;
}
function C1()
{
var a = new A( _valueA );
var b = new B( _valueB );
b.B1( a );
b.B2( a );
}
function C2()
{
var a = new A( _valueA + 200 );
var b = new B( _valueB + 200 );
b.B1( a );
b.B2( a );
}
private var _valueA;
private var _valueB;
}
// Test.as
Debug.SetTraceFunc( trace );
Debug.SwitchClassTrace( true );
var c = new C( 25 , 32 );
c.C1();
c.C2();
c = new C( - 30 , - 26 );
c.C1();
c.C2();
我们看一下输出结果(你会心动么):
[ C ] class created -----valueA: 25 valueB: 32
/---C.C1-----valueA: 25 valueB: 32
| [ A ] class created -----value: 25
| [ B ] class created -----value: 32
| /---B.B1-----value: 32
| | /---A.A1-----value: 25
| | | 32 * 25: 800
| | /---A.A1-----value: 25
| /---B.B1-----value: 32
| /---B.B2-----value: 32
| | /---A.A1-----value: 25
| | | 64 * 25: 1600
| | /---A.A1-----value: 25
| /---B.B2-----value: 32
/---C.C1-----valueA: 25 valueB: 32
/---C.C2-----valueA: 25 valueB: 32
| [ A ] class created -----value: 225
| [ B ] class created -----value: 232
| /---B.B1-----value: 232
| | /---A.A1-----value: 225
| | | 232 * 225: 52200
| | /---A.A1-----value: 225
| /---B.B1-----value: 232
| /---B.B2-----value: 232
| | /---A.A1-----value: 225
| | | 464 * 225: 104400
| | /---A.A1-----value: 225
| /---B.B2-----value: 232
/---C.C2-----valueA: 25 valueB: 32
[ C ] class created -----valueA: -30 valueB: -26
/---C.C1-----valueA: -30 valueB: -26
| [ A ] class created -----value: -30
| [ B ] class created -----value: -26
| /---B.B1-----value: -26
| | /---A.A2-----value: -30
| | | -26 + -30: -56
| | /---A.A2-----value: -30
| /---B.B1-----value: -26
| /---B.B2-----value: -26
| | /---A.A2-----value: -30
| | | -52 + -30: -82
| | /---A.A2-----value: -30
| /---B.B2-----value: -26
/---C.C1-----valueA: -30 valueB: -26
/---C.C2-----valueA: -30 valueB: -26
| [ A ] class created -----value: 170
| [ B ] class created -----value: 174
| /---B.B1-----value: 174
| | /---A.A1-----value: 170
| | | 174 * 170: 29580
| | /---A.A1-----value: 170
| /---B.B1-----value: 174
| /---B.B2-----value: 174
| | /---A.A1-----value: 170
| | | 348 * 170: 59160
| | /---A.A1-----value: 170
| /---B.B2-----value: 174
/---C.C2-----valueA: -30 valueB: -26
输出的是完整的代码的调用流程,将函数中的嵌套调用也描述得一清二楚。
Debug类的代码在本文的结尾,现对其有简要的说明:
它分为两个部分。
第一部分实现了一个出现在flash画面上的文本框来做自动滚动的输出。
用法如下:
CreateTrace( depth, x, y, width, height, lineCount, mc )
它将创建这样一个文本框,参数意义一看便知。lineCount是你在trace结果滚动的过程中想要看到的最大行数。mc是它的父mc,这两个函数都可以缺省,那么lineCount将默认是12行,而父mc将使用level0。(注:这样的文本框只能创建一个)
SetTraceVisible( isVisible )
如果你用CreateTrace创建了输出文本框,这个函数所接受的bool类型的参数能动态地设置文本框是否可见。
xtrace( msg )
其中最为有用的函数。它的用法和trace函数一模一样。
SetTraceFunc( traceFunc )
默认的,当你使用Debug.xtrace的时候,它会去转调Debug.Trace。这个Debug.Trace尝试在CreateTrace创建的文本框里输出。当然你很可能根本就不想用这个文本框作输出,甚至也不会去调用CreateTrace创建什么文本框。你可能钟爱trace函数,只想用flash调试器中的trace窗口(呵呵,如果没有上述迫不得已的原因,我也是)。又或许你有更牛叉的trace方法……这样做:
SetTraceFunc( trace );
把trace函数作为参数传入,告诉它Debug.xtrace去转调trace函数,你也可以使用你自己的牛叉trace函数,比如:
SetTraceFunc( new_char_trace ); //呵呵
这个文本框并不是简单地把新的输出字符加到结尾那么简单。因为如果那样做,随着输出字符串长度的增长,这个过程将会慢到几秒,几十秒甚至更长。
而这个文本框用一个Array作为“后台数据库”。它的文本长度就是你所能见到的那么多。这样做基本不会影响你的程序运行速度(实际上当你设置LineCount为0,或者让输出文本框不可见时,这个影响基本可以忽略,当你用timer对程序效率进行评估的时候,最好这样做)。输出自动滚动,在这个过程中你能够动态地监视程序运行的状况。当然,更有可能你先让程序运行一段,然后回头查看这个过程中的运行记录。这时你应该用鼠标点击这个文本框,当它获得焦点后,才从“后台数据库”中把以往的输出全部读出,联结成一个长字符串(可能有几M长哦)以方便你上下滚动观看或者把所有输出都复制粘贴到其它什么地方。我之前说过,这个过程可是非常慢的,如果有四五十万行的输出,这个鼠标点击的动作可能会让你等上几十秒钟,还要点掉几个是否中止运行的警告对话框。
第二部分才是上面演示的重点。
TraceClass( name, obj, info )
最重要的一个函数。
name:string类型。这个类的名字
obj:一个类的实例,因为一般都放到构造函数里,所以这个参数一般都可以直接传this
info:string类型。这个参数是可选的,每个实例有可能都有不同于其它实例的一些标识,把这些打印出来能够方便你区分到底是哪一个实例的代码在执行。
SwitchClassTrace( classTraceOn )
开启/关闭TraceClass信息的显示。默认是关闭的,你需要先调用这个函数,传递true,将它打开。
SetFunctionTag( func, tag )
ClearFunctionTag( func, tag )
一些特殊的要求通过这两个函数实现
详细的用法请看代码吧,代码很少,不难看懂
它的实现并未用到什么高深的技术,只是用了点小小的技巧。每个类的实例在被创建的时候,它的所有成员函数就都被一个新的函数替换掉了。这个函数先打印一些信息,再调用原函数,完成以后再打印一些信息。在此过程中,控制输出结果的缩进。另外,其中用到一个未公开函数ASSetPropFlags,网上很多文章都提到它,这里就不赘述了。
原创作者Foreverflying,可以随意转载,但请不要删除此行。
附:
// Debug.as
// written by foreverflying
//
class Debug
{
// =========================================================
// in player trace===============================Version 1.2
static public function CreateTrace( depth, x, y, width, height, lineCount, mc )
{
if ( _logObj != undefined ){
return ;
}
if ( mc == undefined ){
mc = _level0;
}
if ( lineCount == undefined ){
lineCount = 12 ;
}
_logObj = new Array();
_logObj.allowChange = true ;
_logObj.isPart = false ;
_logObj.lineCount = lineCount;
mc.createTextField( ' __traceText__ ' , depth, x, y, width, height );
_logObj.traceText = mc[ ' __traceText__ ' ];
var traceText = _logObj.traceText;
var str = ' ' ;
while ( traceText.maxscroll < 2 ){
traceText.text = str;
str += str;
}
traceText.maxRow = traceText.bottomScroll - traceText.scroll + 1 ;
traceText.text = '' ;
traceText._visible = _traceVisible;
traceText.border = true ;
traceText.background = true ;
traceText.backgroundColor = 0xFFFFFF ;
traceText.onSetFocus = function ( oldFocus )
{
_logObj.allowChange = false ;
if ( ! _logObj.isPart ){
return ;
}
if ( _logObj[ 0 ] == undefined ){
return ;
}
var s = _logObj[_logObj.length - 1 ];
s = s.charAt( s.length - 1 );
s = s == ' ' ? '' : ' ' ;
for ( var i = 0 ; i < this .maxRow - lineCount; i ++ ){
s += ' ' ;
}
this .text = _logObj.join( ' ' ) + s;
_logObj.isPart = false ;
this .scroll = this .maxscroll;
}
traceText.onKillFocus = function ( newFocus )
{
_logObj.allowChange = true ;
if ( _logObj.isPart ){
var myText = _logObj.traceText;
if ( ! _logObj.lineCount ){
myText.text = '' ;
return ;
}
var i = _logObj.length > lineCount ? _logObj.length - lineCount : 0 ;
var str:String = _logObj[i];
for ( ++ i; i < _logObj.length; i ++ ){
str += ' ' + _logObj[i];
}
myText.text = str;
myText.scroll = myText.maxscroll;
}
}
SetTraceFunc( _traceFunc );
}
static public function SetTraceVisible( isVisible )
{
_traceVisible = isVisible == true ;
_logObj.traceText._visible = _traceVisible;
if ( ! _logObj.lineCount || ! _logObj.allowChange || ! _traceVisible || _logObj.length == 0 ){
return ;
}
var myText = _logObj.traceText;
var lineCount = _logObj.lineCount;
var i = _logObj.length > lineCount ? _logObj.length - lineCount : 0 ;
var str:String = _logObj[i];
for ( ++ i; i < _logObj.length; i ++ ){
str += ' ' + _logObj[i];
}
myText.text = str;
myText.scroll = myText.maxscroll;
}
static public function SetTraceFunc( traceFunc )
{
_traceFunc = traceFunc == undefined ? Trace : traceFunc;
}
static public function xtrace( msg:Object )
{
var indent = '' ;
for ( var i = 0 ; i < _indent; i ++ ){
indent += ' | ' ;
}
_traceFunc( indent + msg );
}
static private function Trace( msg:String )
{
if ( _logObj == undefined ){
return ;
}
var myText = _logObj.traceText;
var lineCount = _logObj.lineCount;
_logObj.push( msg );
_logObj.isPart = true ;
if ( ! _logObj.lineCount || ! _logObj.allowChange || ! _traceVisible ){
return ;
}
var i = _logObj.length > lineCount ? _logObj.length - lineCount : 0 ;
var str:String = _logObj[i];
for ( ++ i; i < _logObj.length; i ++ ){
str += ' ' + _logObj[i];
}
myText.text = str;
myText.scroll = myText.maxscroll;
}
static private var _traceFunc = Trace;
static private var _traceVisible = false ;
static private var _indent = 0 ;
static private var _logObj;
// =========================================================
// TraceClass====================================Version 1.1
static public function TraceClass( name, obj, info )
{
if ( _classTraceOn == false ){
return ;
}
xtrace( ' [ ' + name + ' ] class created ----- ' + ( info == undefined ? '' : info ) );
_global.ASSetPropFlags( obj.__proto__, null , 6 , 1 );
var control;
for ( var i in obj ){
if ( i != ' constructor ' && typeof (obj[i]) == ' function ' ){
if ( control == undefined ){
control = new Object();
control.exclusiveCount = 0 ;
}
if ( obj[i] != undefined && obj[i].control == undefined ){
obj[i] = createRecallFunc( obj[i], name + ' . ' + i, control, info );
}
}
}
}
static public function SwitchClassTrace( classTraceOn )
{
_classTraceOn = classTraceOn == true ;
}
// 1 : only trace func
// 2 : not trace func
// 4 : not print trace info for func, but indent changes
static public function SetFunctionTag( func, tag )
{
if ( func.tag == undefined ){
return ;
}
func.tag |= tag;
if ( ( tag & 1 ) != 0 ){
func.control.exclusiveCount ++ ;
}
}
// clear the tag setted, if tag is 0, clear all tags
static public function ClearFunctionTag( func, tag )
{
if ( func.tag == undefined ){
return ;
}
tag = tag == 0 ? func.tag : tag;
func.tag &= ~ tag;
if ( ( tag & 1 ) != 0 ){
func.control.exclusiveCount -- ;
}
}
static private function createRecallFunc( orgFunc, funcName, control, info ):Function
{
var ret = new Function(
function ()
{
return TraceCall( this , arguments );
}
);
ret.orgFunc = orgFunc;
ret.funcName = funcName;
ret.control = control;
ret.info = info;
ret.tag = 0 ;
return ret;
}
static private function TraceCall( thisObj, arg )
{
var callee = arg.callee;
var callTrace = true ;
if ( callee.control.exclusiveCount > 0 ){
callTrace = ( callee.tag & 1 ) != 0 ;
} else if ( ( callee.tag & 2 ) != 0 ){
callTrace = false ;
}
if ( callTrace ){
if ( ( callee.tag & 4 ) != 0 ){
_indent ++ ;
} else {
OnCallBegin( callee.funcName, callee.info );
}
}
var ret = callee.orgFunc.apply( thisObj, arg );
if ( callTrace ){
if ( ( callee.tag & 4 ) != 0 ){
_indent -- ;
} else {
OnCallEnd( callee.funcName, callee.info, ret );
}
}
return ret;
}
static private function OnCallBegin( funcName, info )
{
xtrace( ' /--- ' + funcName + ' ----- ' + ( info == undefined ? '' : info ) );
_indent ++ ;
}
static private function OnCallEnd( funcName, info, ret )
{
_indent -- ;
var type = typeof ( ret );
if ( type == ' string ' || type == ' number ' || type == ' boolean ' ){
xtrace( ' |---( ' + ret + ' ) ' );
}
xtrace( ' /--- ' + funcName + ' ----- ' + ( info == undefined ? '' : info ) );
}
static private var _classTraceOn = false ;
}