由Flash中的异步,同步联想到setInterval教程
21视频教程网3月28日整理
今天去了一趟书店,看了很多Flash的书,那叫一个垃圾。倒不是说写得不好,而是有些地方明显回避。其实骂“垃圾”主要是因为看到一些立着“难点”、“高级”牌坊的书。一遇到真正麻烦的情况,就撤了。买“突破难点”就是为了了解难点如何突破嘛,那些众人皆知的事情本应该是另外相关入门书的任务。比如 setInterval ,几乎没看到一本负责任讲得深入的。
当然,我并不是通过“贬低”这些不负责任的作者们来提高自己,在Flash中控制异步同步并非我的长处,所以肯定会存在一些问题!我也希望希望大家多多提出自己的评论、想法。
言归正传:其实异步、同步的问题在Flash开发中,似乎并没有引起太多人的关注。一来设计师们认为 loadMovie 这样的东西封装得很好了,根本不需要深入考虑其细节,即便是更加复杂一点的工程,用 MovieClipLoader 来运筹帷幄,似乎在异步、同步的问题上也没有遇到什么麻烦。
异步、同步,并不能简单说哪个好,哪个不好。要是站在易用性、用户体验的角度来说,异步当然更好。每一次的操作不必等待其他请求就能直接给予用户反馈。当然,也不能完全异步操作。细节到一个集成电路的运作,大到使用某应用程序完成某一项工作,都不可能每次都在上一件任务都不做完的情况下就匆匆执行用户的下一个指令。特别是Windows图形界面的出现、网络通信工具的出现以及AJAX的大张旗鼓的宣传,人们似乎已经忘记了计算机内核最稳定以及最基本的同步运算原理。
当然,千万不要认为我又是在这里鼓吹同步如何好、如何稳定。只是最近做到的一些项目中对异步造成的麻烦有所感叹而已。要是只有同步,估计现在计算机只能计算加减乘除了。我只是想说,异步同步均有其使命,关键看人如何使用了。
不扯远了……说到Flash中的异步同步,不得不提及两条非常非常重要的语句:
loadMovie、setInterval
关于具体的使用,不想多说,到处都有,我这里也有一些教程中有涉及。仅仅想啰嗦一下,不要用loadMovie了,改用MovieClipLoader(当然,无法兼容Player6.0以下)
不妨先结合一个具体的例子来说明一下setInterval和loadMovie(MovieClipLoader)中,有关同步异步的一些概念。
首先在同级目录下,建立两个fla文件,分别命名为loader.fla, loadee.fla
仅在第一帧分别写下(理解成伪码吧,毕竟只是为了帮助大家理解空写出的代码:我没有编译,不敢保证直接拿过去就没有问题,但是主要思想都是很清楚地。)
//这里是loader,用于加载loadee.swf的源码,以及自身设定的一些交互、流程,通过trace语句来进行试验
var myMCL:MovieClipLoader = new MovieClipLoader ();
var l:object = new Object ();
l.onLoadStart = function (t)
{
trace ("start@: " + t);
};
l.onLoadComplete = function (t)
{
exeThis ();
};
l.onLoadInit = function (t)
{
trace (loadee); //结论1
};
myMCL.addListener (l);
myMCL.loadClip ("loadee.swf", _root.createEmptyMovieClip ("temp_mc", _root.getNextHighestDepth ()));
//loadMovie ("loadee.swf", _root.createEmptyMovieClip ("temp_mc", _root.getNextHighestDepth ()));
function exeThis ()
{
var intervalObj:object = new Object ();
intervalObj.someInterval = setInterval (func1, 100, 1);
var intervalAnothoer:Number = setInterval (func2, 100, 1);
function func1 (q)
{
trace ("==Loader==Func1==");
trace (intervalObj.someInterval);
}
function func2 (q)
{
trace ("==Loader==Func2==");
trace (intervalAnothoer);
}
}
bb_btn.onRelease = function ()
{
bb = setInterval (exeThis, 1000); //setInterval中套setInterval
trace (bb);
};
stop ();
//这里是loadee.fla,发布成loadee.swf以供loader.swf加载。
_global.loadee = true; // 结论1
var _intervalObj:object = new Object (); // 结论2
_intervalObj.someInterval = setInterval (func1, 100, 1);
var _intervalAnothoer:Number = setInterval (func2, 100, 1);
function func1 (q)
{
trace("==Loadee==Func1==");
trace(_intervalObj.someInterval);
}
function func2 (q)
{
trace("==Loadee==Func2==");
trace(_intervalAnothoer);
}
stop ();
结论:
1、这个变量用作测试,通过trace其值可以一目了然MovieClipLoader的onLoadInit的必要性。
2、不妨构造一个对象来存放interval整数,来说明interval不论存放于何处都“傻傻地闭目自增”。
我们这里进行了一些trace,结果会是:1 2 3 4 5 ... 这样的序列。下面简要解释一下:
首先,每一个setInterval方法,都会返回一个整数,通过对这个整数进行clearInterval操作,可以停止setInterval的反复执行。我们完全可以把这样一个整数,intervalID,理解成为一个“缰绳”,一面setInterval导致一些函数沦为“脱缰野马”。
intervalID 这样一个“缰绳”对应一个整数,每一次setInterval它都自增1。然而正是这种每一次setInterval都毫无条件进行自增的流程,很容易造成“脱缰野马”的惨状。一旦某些setInterval在clearInterval之前把intervalID给破坏掉了,就很难找寻了。这似乎有点像我国古代里的“刻舟求剑”的寓言。
由于大量的教程、书籍都强调了clearInterval的重要性,因此遗忘clearInterval所造成的“脱缰野马”我这里暂不讨论。
我想列举一些其他的例子来讨论此问题。
比如,场景有两个按钮A和B,按下A之后,开始setInterval;按下B,就把这个intervalID给clear掉,这样的程序很好写:
var someInterval:Number;
btn_a.onRelease = function ()
{
someInterval = setInterval(myFunc, 100);
}
btn_b.onRelease = function ()
{
clearInterval(someInterval);
}
如果用户按照设计者的套路出牌,肯定不会有漏网之鱼:每一次点击a之后,都先“老老实实”地把b给点掉,再去点a。
可是如果用户点两下a呢?结果是myFunc被以间隔(100/2)ms地调用。(这也无可厚非,也许用户觉得我们需要更频繁的提速之类,而且也可以通过程序来限制间隔下限。)这时候点一下b,频率会降低,因为消除了第二个interval,可是如果我再点一下,就“刻舟求剑”了,myFunc仍然在大胆的反复执行着!
问题在于何处?其实大家也清楚,第二次点击的时候,someInterval的值变了,由1变成了2,我们不论再执行 clearInterval(someInterval)多少次,实际上都是在执行clearInterval(2);我们需要一并剔除掉的“1”这个 interval成了掉在水里的剑,一去不复返了。
如何找回“1”?很多人为之而苦恼,甚至抱怨Macromedia没有提供好的方法来管理setInterval。我觉得这实在不是什么好的习惯。仔细想想,setInterval是什么时候推出的,现在都更新到8了,还用老方法。要是真的有问题,不早就更改了么。我最讨厌一遇到问题就抱怨Flash本身的人,包括原来的我自己。通过大量的项目经验,我得出这样的结论:遇到问题最好的办法就是花时间猛攻这个问题,而不是抱怨你的开发平台。
也许有点情绪化了,还是回到正题,如何找回“1”呢?如何把“脱缰野马”给拉回来呢? 仔细想想,问题不在于你如何去找回它,而在于如何保护好它!注意这个词,保护!
其实有一个非常好的保护思想,那就是计算机中“栈”的概念。如果你是计算机相关专业的学生,那就根本不用我再罗嗦了,汇编中用POP保护栈你肯定烂记于心了!但是我想玩Flash的,大多数未必是相关专业的,所以还是来解释一下吧。
有时候我们有一些变量或者寄存器(姑且理解为变量吧)。我们要对它频繁地对它(不可绕开)进行一些操作,而它的每一个值都对我们的整个流程非常重要。我们必须在每一次对它操作之前把它保存下来,然后操作结束之后,把这个保存好的值拿出来以备其用。计算机中有一个“栈空间”,你可以通过push,把某一个值给存进取,然后通过pop取出。
Flash中没有栈,但是我们可以用数组代替:)而Macromedia Flash给我们提供了一个强大的数组类,让我们足以模拟栈的操作!
查一查帮助,Array.push和Array.pop方法。一目了然。
下面给出解决代码。上面我讲得很清楚,而且代码也不长,我想没有注释大家也能理解了,后面也有swf演示:)
if (myTempInterval1_arr == undefined )
var myTempInterval1_arr:Array = new Array();
if (myTempInterval2_arr == undefined )
var myTempInterval2_arr:Array = new Array();
var intervalID:Number;
a_btn.onRelease = function ()
{
intervalID = setInterval(someFunc,100,this);
myTempInterval1_arr.push(intervalID);
}
b_btn.onRelease = function ()
{
for (var j in myTempInterval1_arr)
{
clearInterval(myTempInterval1_arr[j]);
myTempInterval1_arr.pop();
}
clearInterval(intervalID);
}
c_btn.onRelease = function ()
{
intervalID = setInterval(someFunc,100,this);
myTempInterval2_arr.push(intervalID);
}
d_btn.onRelease = function ()
{
for (var j in myTempInterval2_arr)
{
clearInterval(myTempInterval2_arr[j]);
myTempInterval2_arr.pop();
}
clearInterval(intervalID);
}
reset_btn.onRelease = function ()
{
a_btn._x=
c_btn._x=
}
function someFunc(_m:MovieClip)
{
_m._x++;
}
最后,有必要强调一下,执行顺序的重要性。
Flash中,帧上的代码总是会先执行。执行完毕后再进行图形渲染(即输出到屏幕)。这里要说明两点:
一、如果你这一帧上有一个mc,名叫"myMC",在屏幕的中央。那么,在执行帧代码的时候,它是被初始化好了的,只不过没有渲染而已。
二、你在帧上对myMC进行操作,比如设置myMC._x = 0; 那么这句会立即执行,显示出来的时候,这个mc会直接出现在屏幕的左端,而不是“从中间跳到左端”。
相信这样解释对理解帧代码的执行顺序会有很大帮助。
然后就是mc代码的执行顺序。
同一层上,被先创立的mc,代码会先执行。比如依次建立三个mc,名字分别为a_mc,b_mc,c_mc,分别写上onLoad的代码,输出1、2、3;最终不管你的mc位置如何,中途如何编辑过,屏幕上会依次输出123,而不是321或者231之类的结果。
对不同的层而言,越靠上,则优先级越靠前。比如我把上面三个mc中最后一个建立的“c_mc”移动到最上层,则显示输出为312。
gotoAndPlay和gotoAndStop只能命令时间轴。举个例子也就是说以下代码:
// Frame 1 : nothing but this:
var a:Number = 100;
gotoAndStop(5);
trace(a);
// Frame 5 : nothing
这里执行结果会显示100:不管gotoAndStop后面的代码有多少统统执行完毕才跳转帧。
注意常犯错误:
gotoAndStop(3);
var m:Number = setInterval (myfunc,1000);
很多人认为这样会产生延迟1000ms-跳转的效果,其实不然。因为固然会执行var m:Number = setInterval (myfunc,1000); 但是它本身的执行基本不耗任何时间。如果要实现延迟跳转,必须用如下方法:
var m:Number = setInterval (myfunc,1000);
myfunc = function ()
{
gotoAndStop(somewhere);
//NULL
}
好了,大功告成,个人觉得还是勇敢地面对了setInterval的一些难点,并且提出了原创的解决方案(push-pop法),行文比较散,但涉及的内容我觉得还是挺实在的,希望对您真的有所帮助,