◆理解函数的目的
◆给函数传递参数 ◆理解函数的变量作用域的重要性 ◆编写自定义函数 ◆从函数返回值 ◆用setInterval()创建递归函数 通过使用函数,就可以创建可重用的代码、可读的代码、灵巧的代码。有了函数,就可以写出有效的、结构精巧的、维护得很好的代码,而不是冗长的、笨拙的代码。 一、理解用函数进行编程
函数是一种革新。写代码没有函数,就像出版图书没有印刷机,出版业是如此没有生产力,如此没产量。有了印刷机,只制一次版,就可以从那个版复制出许多副本了。印刷机是一种革新。同样地,如果编程序没有函数,就不得不一次一次地写出代码的每一行。但是,当写出一个函数时,就可以将多条语句封装在一起,就可以重复地调用那个函数(即那些语句的组),而不用重复写相同的代码了。那就巧妙多了。函数是一种组织起一个代码块的方法,该代码块直到从其主流程中调用(直接地或间接地)时才执行。换句话说,函数是一种将调用时才执行(不调用不执行)某个特殊任务的代码块包装在一起的方法。函数比非结构化编程更具优势。这些优势包括: ◆ 通过消除混乱和冗余的代码,使代码更具有可读性。 ◆ 通过重复使用函数而不是每次重复输入整个代码块,使程序更加有效率。 ◆ 函数成为了进行修改的中心点。在函数中做个修改,该修改就能被应用到每个调用该函数的实例中。 ◆ 编写成熟的函数可以在许多程序中重复使用。因此,可以开发出一个可被用于建立各种程序的函数库,而不需要每次从打草稿开始写脚本。 ◆ 包装在一个函数中的代码提供了进行用户交互的基础。如果没有函数,应用程序就像一个单独的程序那样运行。有了函数,一个用户发起的动作就可以调用一个函数。 二、定义自定义函数
我们已经知道在ActtionScript代码中使用函数的某些优点了。现在,需要学习如何来写函数。写一个函数也被称为“定义”或“声明”一个函数。函数的语法:
function 函数名(参数):返回数据类型{
函数体 ) 在函数的语法中,要注意如下几个关键点:
◆function关键字告诉Flash,正在声明一个函数。当定义一个函数时,必须像上面那样包括function关键字。 ◆函数应该遵守变量的命名规则。就像命名变量那样,给函数取一个能表示其功能的名字是一个好想法。 ◆所有函数的定义都必须在函数名字后面包括一对圆括号。在圆括号中的“参数”可以没有。在后面可以读到有关参数的更详细的内容。但是,无论一个函数是否定义了参数,都必须在定义中包括一对圆括号。 ◆圆括号后面接着就是冒号和一个有效的数据类型名字。数据类型是函数将返回的数据的类型。在后面会看到如何返回数据。使用Void表示该函数不返回值。 ◆函数体是由一对开始和结束波浪式大括号({})定义的。 现在已经知道基本的语法了,那就看一个非常简单的函数例子吧: function displayGreeting():Void{
trace("Hello."); } 三、调用函数 function displayGreeting():Void{ 当测试该影片时会看到,尽管在代码中有一个trace()动作,但什么也没发生。所以,既然知道了如何“定义”函数,就需要学习如何通过“调用”它们而在程序中使用它们。为了调用一个函数,需要使用该函数的名字,并后跟一个圆括号(它被称为“函数调用运算符”)。当调用一个函数时,对函数的调用本身就是一个语句。因此,应该在该语句的后面使用一个分号。下面的例子定义了一个函数,然后调用它。如果想自己学习和测试它,只需将该代码放置在主时间轴的第1帧即可。trace("Hello."); } function displayGreeting():Void{ 当测试该影片时,在Output窗口中显示如下内容:trace("Hello."); } displayGreeting();//调用函数 Hello. 四、传递参数 function displayGreeting(姓名:String):Void{
trace("Hello."+姓名) } 一旦用这种方式定义了该函数,就可以调用它了,并给它传递不同的参数值。某些例子如下所示: //不能使用var关键字。 如果想在函数中使用多个参数,该怎么办呢?很简单,当定义函数时,可以声明用逗号分隔开的多个参数。同样地,当调用该函数时,可以给它传递多个值,只需简单地用逗号分隔这些值即可。如下是具有多个参数的dlsp]ayGreetlng()函数的例子:function displayGreeting(var 姓名:String):Void{ trace("Hello."+姓名) } //不能使用分号。 function displayGreeting(姓名:String;):Void{ trace("Hello."+姓名) } //不要试着在圆括号中初始化变量。 function displayGreeting(姓名:String="王麻子"):Void{ trace("Hello."+姓名) } function displayGreeting(姓名:String,体重:Number):Void{ trace(姓名+"的体重是:"+体重); } displayGreeting("张三",50);//显示:张三的体重是:50 ,读者可能注意到了,当开始将越来越多的参数添加到一个函数定义的参数列表中时,代码就开始超出编辑器的边界了。可以在Actions面板中启用自动换行功能。还可以在定义函数时将每个参数(或参数组)放置到新的一行中。这是一个通常采用的习惯,因为它能更容易地阅读一个由许多参数组成的函数的参数列表。语法是相同的,区别仅在于:参数列表中的每个参数都放在新的一行中,以便使它更容易阅读。例如: function displayGreeting(姓名:String, 体重:Number):Void{ trace(姓名+"的体重是:"+体重); } 注意:定义函数的参数个数与调用时传递的参数个数不一定要相同,当然,如果该有的参数没有的话,可能函数不会正常工作,多个参数是按先后顺序依次“对号入座”的。 function 引用测试(a:Number):Number{ a++; return a; } var b:Number=5; var c:Number=引用测试(b); trace(b); trace(c); Output窗口会显示如下内容 function move(mA:MovieClip,X:Number,Y:Number):Void{ mA._X=x; mA._y=y; } move(mBox,100,100); 上面这个例子将名为mBox的MovieClip对象移动到舞台的(100,100)处。 2、使用arguments属性。
现在所看见过的函数要么不使用任何参数,要么参数被声明为圆括号中的参数列表。但是,不管一个函数是否声明了任何参数,传递给函数的所有参数都被保存在一个名为arguments的特殊数组中。每个函数都有一个arguments变量(对象),当调用函数的时候,就在该函数中创建该变量。ActionScript并不强求函数定义中的参数个数与调用时传递给该函数的参数个数一致。其意思是在调用时没有被传递、但在函数的参数字符串中被定义过的任何值,都会具有一个undeflned值;而在函数调用中传递的任何值,如果在函数的参数定义中没有它,就会被忽略。因此,完全可以定义一个没有参数的函数,但仍然可使用arguments对象传递参数。下面是一个把arguments对象作为数组使用的例子: function traceParams():Void{ for(i=0;i<arguments.length;i++)( trace(arguments[i]); } } traceParams("one","two","three"); one two three 在大多数函数中,声明参数要好得多。当重载一个函数或遇到相似情况时(参见“重载函数”一节),arguments对象很有用。每一个arguments对象都有两个特殊的引用函数的属性。尽管不常使用这些属性(caller和callee),但在某些情况下(特别是在开发高度抽象的函数时)这些属性却有用。如果有一个函数调用当前函数的话,arguments对象的caller属性就返回对这个函数的引用。如果没有另一个函数调用当前函数的话,caller属性就有一个null值。 function function1():Void{ function2(); } function function2():Void{ if(arguments.caller==function1) trace("function2 called from function1"); else trace("function2 not called from function1"); } function1();//Output:function2 called from function1 function2();//Output:function2 not called from function1 Var fFactorial:Function =function(nOperand:Number):Number{ 五、从函数返回一个值if(nOperand>0){ return nOperand * arguments.callee(nOperand-1); }else{ return 1; } } 到现在为止,主要介绍了函数作为子程序的这一点。也就是说,函数可以将主程序分解为更小的、更易管理的片段。一方面,当函数用那种方式作为一个子程序时,函数并不需要返回一个值。另一方面,某些时候想创建一个函数,用它进行某些计算或操作,然后返回一个值。可以在一个函数中使用return语句来返回一个特定的值。return语句的语法如下所示: return value; 当使用return语句从一个函数返回一个值时,应该指定要被返回的数据类型(在函数定义的圆括号后面指定)。在此之前的例子中,返回类型是Void(表示无返回)。但是,当返回一个字符串时,就应该将返回的数据类型设置成String;当返回一个数字时,就应该将返回的数据类型设置成Number,等等。下面是一个计算矩形面积的函数的例子,其返回值是数字: function 求面积(nA:Number,nB:Number):Number{ 只要遇到return语句,Flash就退出该函数。因此,如果在return语句后面还有其他剩余代码,就不会再执行它们了。例如:
var nArea:Number=nA * nB; return nArea; } function 求面积(nA:Number,nB:Number):Number{ var nArea:Number=nA * nB; return nArea; trace("The area is:"+nArea); } function findMatchingIndex(aTitles:Array,sTitle:String):Number{ 不管函数做什么,只要它返回一个值,就完全可以将其作为表达式的一部分来调用该函数。例如,可以用下面的方法来使用“求面积”函数:
//循环遍历数组中的所有成员。 for(var i:Number=0;i<aTitles.length;i++){ //如果成员之一匹配sTitle的值,就返回相应的索引。这会导致该函数停止执行。 if(aTitles[i]==sTitle){ return i; } } //如果没有发现匹配,就会遇到这条语句(仅此而已)。 return null; } var nArea:Number=求面积(6,6);//返值存入变量 也可以: var nArea:Number=求面积(6,3) * 5;//函数参与计算 6; 下面也一样: 求面积(6,6); 我们希望按某些有意义的方法来使用返回的值。可以像使用变量那样来使用一个会返回值的函数。我们已经见过用于赋值语句中的“求面积”函数了。下面是另外一个例子,在该例子中,函数作为条件表达式的一部分使用: if(求面积(6,6)>18){ trace("The area is more than 18."); } 六、引用函数 function 求面积(nA:Number,nB:Number):Number{ var nArea:Number=nA * nB; return nArea; } var fArea:Function=求面积; trece(fArea(6,6));//36 下面将看到如何将匿名函数赋予变量。 function(参数):返回类型{
函数体 }; 读者可能注意到了,标准的函数声明和匿名函数声明在语法上很相似,仅有两点不同。第一,匿名函数没有函数名。第二,匿名函数后应该跟一个分号,这在标准函数的声明中是不需要的。正如前面所述,这主要是想将匿名函数赋予一个变量。否则,当该函数被定义之后,它就“离开了”作用域(即变成未定义的了)。下面是将匿名函数赋予一个变量的例子: var fSayHi:Function=function(sName:String):Void{ trace("Hi,"+sName); ); fSayHi("Joey");//显示:Hi,Joey 就像所看到的那样,可以使用被赋予该匿名函数的变量来调用该匿名函数。实际上,匿名函数大家经常都在用,回想一下,按钮事件函数的写法,如: btn.onPress=funceton(){
...... }
十一、重载函数
重载函数是指,使多个函数具有相同的名字但具有不同数量和/或类型的参数。在许多情况下这可能是有用的。例如,可能有一个名为caIculateArea()的函数,它基于两个参数(各个边的长度)来计算矩形的面积。但可能还希望有一个calculateArea()函数,它基于一个单独的参数(半径)来计算圆的面积。问题是,正如已经提到过的那样,ActionScript并不要求函数定义中的参数数量与传递给它的参数的数量一致。这就是说,不能有两个具有相同名字的函数,即便它们有不同数量的参数。因此,不能真的用ActionScript来重载函数。相反,可以在函数中使用if语句或switch语句来检查参数的数量而模仿函数的重载。下面是一个例子,它说明可以如何写一个函数,使其根据传递给它的参数的数量(由arguments.length决定)计算矩形的面积或者圆的面积。这并不是一个严格意义上的重载函数,而是ActionScript的等价物。 function calculateArea():Number{ switch(arguments.length){ case 1: var nRadius:Number=arguments[O]; return(Math.PI*(nRadius*nRadius)); case 2: var nA:Number=arguments[O]; var nB:Number=arguments[1]; return(nA*nB); default: return null; } } 十二、为可重用而编写函数 当写一个函数时,要记住易用的或可重用的代码的重要性。理想的情况是使函数尽可能地通用和尽可能地包装好。函数一般应该像一个黑箱一样进行操作。这就是说,函数的活动性从本质上讲应该是不依赖于程序的其他部分的。一个写得很好的函数应该可以被用到许多不同的程序中,就像一把万能钥匙可以开不同的锁一样。应该用可重用性的观念来写函数。当写通用函数时,要记住如下几点: ◆通常,不要使用在函数外面定义的变量——在函数中使用的变量(和对象)应该是在函数中声明的或作为参数传递给函数的。如果需要将一个值赋予一个将在函数的作用域之外使用的变量,则考虑使用一个return语句来代替。因为一个函数一次仅能返回一个值,所以可能会发现用一个return语句似乎有点局限。如果在函数中的确是这样的话,那么也许就会发生下面两件事之一:要么想返回的值是相关的值,那么可以将它们放入一个数组或一个对象中,然后返回它们;要么它们是不相关的值,那么就应该将函数拆散成多个函数。但也可能有例外。某些时候,只想用一个函数将一个影片中的某些功能组织到一起,以便将代码组织成子程序。在这种情况下,直接访问在该函数外面声明的变量和对象还是可以接受的。 ◆给函数取一个能描述其任务的名字——当再次看到函数时,就可以很容易地知道该函数是干什么的了。如果因为函数要做许多事情而不好给它取名,请考虑将那个函数拆散成多个函数。即便是通用函数,它们也应该执行特定的任务。虽然这些指导方针通常是有用的,但有的时候它完全不适合写一个真正通用的函数。如果写一个函数的任务对正在写的程序来说非常特殊,那么将它写得太通用了也许并没什么用。 十三、使用内置函数 前面已经学过如何在ActionScript中创建一个自定义函数了。通常,当人们谈到函数时所指的就是这些自定义函数。但是,在ActionScript中还有许多其他“内置函数”,基本上可以像使用自定义函数那样来使用它们。Actions工具箱中包含一个名为Global Functions的文件夹,在这个文件夹中是包含所有内置函数的子文件夹。这些函数中的许多已经被类和方法代替了,因此最好使用这些新的替代品。例如,所有的时间轴控制、影片剪辑控制和打印函数都已经被方法代替了。但仍有某些全局的内置函数是有用的。这些函数包括: ◆fscommand()_一仅在非常特殊的情况下才使用这个函数。fscommand()函数能使Flash影片与播放器进行通信。 ◆setInterval()/clearInterval()——这些函数能够指示Flash按特定的、固定的间隔去调用其他函数。请参见下面的“创建间隔函数”中的详细内容。 ◆escape()/unescape()——这些函数被用于在文本和安全URL格式之间进行转换。 ◆getTimer()——getTimer()函数返回自从Flash影片开始播放以来的毫秒数。这在某些定时处理(不要求很高的准确性和精确性)中可能有用。例如,想在影片“时间到了”之后做某些循环,在这些情况下,getTimer()就是一个合适的函数。例如,可能有一个等待来自服务器的响应的影片,但是,如果30秒钟之后还没有得到响应,可能就想停止等待并提醒用户服务器没有响应。 ◆trace()——trace()函数在本书中到处可见。在测试Flash应用程序显示信息时,它非常有用。 ◆isFinite()/isNaN()——这些函数测试一个值是否是有限的,甚至是否是一个有效的数字。 ◆parseFloat()/parselnt()——这些函数从一个字符串中解析一个数字。 十四、创建间隔函数 可以用函数来做的一件非常有用的事情就是:利用setInterval()命令创建间隔函数。通过使用setInterval()函数,可以指定一个函数和一个连续调用该函数的时间间隔(以毫秒为单位)。该函数返回一个ID(标识),该ID可以被用于稍后停止该间隔。下面是setInterval()函数与一个函数一起使用时的标准语法: setInterval(function,interval[,param1…,paramN]) function writeMessage(sName:String,sMessage:String):Void{ trace("Hello,"+sName+"."+sMessage); } var nwriteInterval:Number=setInterval(writeMessage,1000,"Joey","Good morning."), function writeMessage(sName:String,sMessage:String):Void{ trace("Hello, "+sName+"."+sMessage); } var sNameParam:String="Joey"; var sMessageParam:String="Good morning."; var nWriteInterval:Number=setInterval(writeMessage,1000,sNameParam, SMeSsageParam); function traceTimer():Void{ trace(getTimer()), } var nTimerInterval:Number=setInterval(traceTimer,200); function moveRight(mA:MovieClip):Void{ mA._x++; updateAfterEvent(); } Var nMoveRInterVal=setInterval(moveRight,10, mCircle); var nTimerInterval:Number=setInterval(function(){trace(getTimer());},200); 现在,知道如何为一个要被调用的函数设置一个间隔了,读者或许还想知道如何使一个正在被不停调用的函数停下来。换句话说,就是想知道如何清除间隔。通过调用clearInterval()函数就能非常简单地做到这一点,clearInterval()函数使用一个单独的参数,即表示应该被清除的间隔的ID。请记住,setInterval()会返回一个可以被用于指示该间隔的ID。下面的代码停止一个间隔,该间隔的ID已经被赋予 nTlmerInterval 了: clearInterval(nTlmerInterval); 十五、小结 ◆函数是一种将代码块组织到一起的方法,可以通过名字或引用来调用它们,从而一遍又一遍地使用它们。 ◆函数可以作为子程序,即它们可以执行某些运算并返回一个值。 ◆函数可以被命名,也可以匿名。这两种类型都有不同的优点和缺点。 ◆通过使用为函数创建的arguments对象,可以调用一个调用函数,递归地调用一个匿名函数, 并且按数组而不是按单独的各个变量来使用传递给函数的各个参数。 ◆定义可以在许多不同环境中使用的通用函数是值得的。 |
|