Tags:ActionScript
第六节 变量的作用域 此前,我们将变量附着在Flash文档主时间轴的某个单独帧上,并籍此学习了如何创建变量并取回变量的值。当某个电影文档中包含多个帧以及多个电影剪辑时间轴时,变量的创建和值的取回就变得略微复杂了。
为了清楚地阐释,我们提出几个设想。
1.设想一 假设我们在时间轴的第一帧上创建一个变量x,随后将其值设置为10: var x; x = 10;
接着,在下一个帧(帧二)中,我们附着下列代码: trace (x);
当我们测试电影时,在Output窗口中会出现什么?我们在帧一中创建变量,而试图在帧二中取回它的值,我们的变量仍然存在吗?
答案:是的。
提示:当你在某个时间轴上定义了某个变量之后,你可以在该时间轴的任何帧上访问这个变量。
2.设想二 假设我们创建并设置变量x,不过和设想一不同的是,我们并不把变量设置代码直接放置在第一帧中,取而代之的是将这些代码放置在帧一中的某个按钮上。接着,在帧二中,我们加入与设想一同样的代码: trace (x);
设想二也能正常工作吗?
答案:是的。因为变量x被附着在按钮上,而按钮又附着在主时间轴上,可以推理出变量x间接地附着在主时间轴上;因此,我们可以象设想一那样在帧二中访问该变量。
3.设想三 假设我们在主时间轴的第一帧上创建名为secretPassword的变量。当电影运行时,为了获得该电影中某个特定区域的访问权,用户必须输入正确的密码。
除了在帧一中声明变量secretPassword之外,我们还要创建一个函数;该函数校验用户的输入是否与真正的密码相吻合。下面是该脚本代码: var secretPassword; secretPassword = "yppah";
function checkPassword () { if (userPassword == secretPassword) { gotoAndStop ("accessGranted"); } else { gotoAndStop ("accessDenied"); } }
假设我们在第三十帧处要求用户输入他的密码。为了获得访问权,用户将密码输入到一个名为userPassword的输入文本域(input text field)变量中;然后我们在帧一的脚本中用函数checkPassword ()对比变量secretPassword和上述输入文本域变量userPassword是否一致。如果我们的密码校验代码被定义在第一帧上,而变量userPassword直到第三十帧才被定义,那么当我们调用checkPassword ()函数时,变量userPassword存在吗?
答案依然是:是的。即使userPassword被定义在函数checkPassword ()所在帧之后的帧上,它们仍处于同一个时间轴。
提示:在某个时间轴上声明的任何变量对于该时间轴上的所有脚本来说都是可用的 —— 只要该时间轴还存在。
4.变量的可访问性(作用域) 上述提出的三个设想探索了有关作用域的问题。一个变量的作用域描述了该变量在何时以及在何处能被电影中的代码所操作。一个变量的作用域定义了该变量的生存期以及脚本中的其他代码块对它的可访问性。为了确定变量的作用域,我们必须回答两个问题:(1)变量能存在多长时间?(2)在我们的代码中,从哪里能够设置并取回变量的值?
在传统的编程语言中,变量通常被分为两种常规的作用域类型:全局的和局部的。在整个程序中,从任何地方都可以访问的变量称之为全局变量。在整个程序中,只有在有限的一个或几个部分的内部才可以访问的变量称之为局部变量。尽管Flash支持传统的局部变量,但是它并不支持真正的全局变量。让我们来揭示这是为什么。
5.电影剪辑变量 就象我们在前面的三个设想中看到的那样,定义在某个时间轴上的变量对于该时间轴上的所有脚本来说都是可用的 —— 从第一帧到最后一帧 —— 无论该变量被声明在一个帧中或是一个按钮上。但是,如果某个电影中有多个时间轴时会发生什么?是设想四中描述的那样吗?
5.1设想四 假设我们有两个基本的几何图形,一个正方形和一个圆形,都被定义为电影剪辑符号。
在正方形电影剪辑符号的第一帧,我们设置变量x为3: var x; x = 3;
在圆形电影剪辑符号的第一帧,我们设置变量y为4: var y; y = 4;
我们将这两个电影剪辑符号的实例放置在电影文档主时间轴的第一层第一帧上,并将这两个实例分别命名为square和circle。
第一个问题:如果我们将下列代码附着在主时间轴的第一帧上(square和circle的所在帧),在Output窗口中会出现什么? trace (x); trace (y);
答案:Output窗口中什么也不会出现(或出现两个undefined)。变量x和变量y被定义在各自的电影剪辑时间轴中,而非主时间轴中。
提示:附着在某个电影剪辑时间轴上的变量的作用域被限制在该时间轴中。其他时间轴上的脚本(比如本例中主时间轴上的两个trace ()命令)无法直接访问该变量。
第二个问题:如果我们将trace (x)和trace (y)这两个语句放置在square电影剪辑的第一帧上从以取代放置在主电影时间轴的第一帧上,那么在Output窗口中会出现什么?
答案:Output窗口只会出现变量x的值,也就是3,而没有其他东西出现(或者出现3和一个undefined)。变量x的值能够被显示是因为x定义在square电影剪辑的时间轴上,因此能被位于同一时间轴上的trace ()命令所访问。而变量y的值,也就是4,无法在Output窗口中出现,这是因为变量y被定义在circle中,也就是说处于另外一个单独的时间轴上,因此对于处在square电影剪辑时间轴上的trace ()命令来说它是不可见的。
此前,我说过ActionScript不支持真正的全局变量,而现在,你应该可以理解这是为什么了。全局变量是在整个程序中都可以访问的变量,但是在Flash中,一个附着在单独时间轴上的变量只能被该时间轴上的脚本直接访问。因为Flash中的所有变量都定义在各个时间轴上(直接地或间接地),所以没有哪个变量可以保证被电影中的所有脚本直接访问,因此,没有哪个变量能被合理地称为全局变量。
为避免混淆,我们将附着在时间轴上的变量称为“时间轴变量”或“电影剪辑变量”。
不过,使用对象类来模拟全局变量是可能的。为了创建在所有时间轴上都可用的变量,使用以下语句: Object.prototype.myGlobalVariables = myValue;
例如: Object.prototype.msg = "Hello, world!";
这项技术以及其工作原理我们将在以后的章节中讨论。
6.在不同的时间轴上访问变量 尽管在一个时间轴上的变量不能被其他时间轴上的脚本直接访问,但是它们可以被间接地访问。为了在不同的时间轴上创建变量、取回变量值、或为变量赋值,我们可以使用“点语句”(EMCA-262标准中的官方术语为:dot syntax)。点语句在面向对象的编程语言(如Java,C++,和JavaScript)中是一个很普通的标准符号。为了在不同的时间轴上定位某个变量,我们使用下述点语句的措辞通式: movieClipInstanceName.variablName
该通式描述了如何指向另一个时间轴上的某个变量:首先是该变量所在的电影剪辑的名字,随后是点操作符,最后是该变量的变量名。举例说明,在前面的设想中,为了在主时间轴上引用square电影剪辑中的变量x,我们可以这样: square.x
同样,在主时间轴中,我们可以这样引用circle电影剪辑中的变量y: circle.y
有了上述这些引用,我们可以在主时间轴上为square电影剪辑中的变量赋值以及取回它的值,象这样: square.z = 5; // assign 5 to z in square var mainZ; // create mainZ on the main timeline mainZ = square.z; // assign mainZ the value of z in square
你一定注意到了我频繁地使用限定词“主时间轴”。这是因为如果只是使用clip.variable这样的语法,那么我们就不能在circle电影剪辑中引用square电影剪辑中的变量。假设我们在circle电影剪辑的某个帧上的脚本代码中放置一个square.x这样的引用,解释器将试图在circle电影剪辑的内部寻找一个名为square的电影剪辑,但是,我们的square却位于主时间轴上。所以,为了从circle电影剪辑指向包含square电影剪辑的时间轴(在本例中是主时间轴),我们需要一种机制,而该机制带来了两个特殊属性形式:_root和_parent。
6.1_root和_parent属性 _root属性是对电影主时间轴的直接引用。从某个电影剪辑结构的任何嵌套深度中我们都能使用_root来定位主电影时间轴上的变量,象这样: _root.mainZ // access the variable mainZ on the main timeline _root.firstName // access the variable firstName on the main timeline
我们甚至能够将某个引用结合到_root上并借此形成对电影剪辑实例的引用,从而潜入电影的嵌套结构中。例如,变量x在电影剪辑square的内部,而square位于主电影时间轴上,我们可以这样定位x: _root.square.x
上述引用在电影中的任何位置上都可以工作 —— 无论电影剪辑的嵌套结构有多深 —— 因为该引用开始于我们的主电影时间轴:_root。下面是另一个嵌套的例子,演示如何访问变量area,area在实例triangle中,而triangle位于实例shapes的时间轴上,shapes则位于主电影时间轴上: _root.shapes.triangle.area
任何由关键字_root开始的引用都称为绝对引用,因为它们通过电影中固定且永恒不变的“主时间轴”来描述变量的位置。
有时候,我们想引用其他时间轴上的变量但是又不想通过主时间轴来引用(_root)。为此,我们使用_parent属性,该属性引用当前电影剪辑实例所在的时间轴。举例说明,若某段代码附着于电影剪辑square的某一帧上,在该代码段中,我们只要使用下述语法就可以引用square所在的时间轴上的变量: _parent.myVariable
由关键字_parent开始的引用被称为相对引用(或关系引用),相对引用完全与它们所在的电影剪辑的位置有关。
回到以前的例子中,假设我们有一个变量size,该变量被定义在电影的主时间轴上。我们将一个名为shapes的电影剪辑实例放置在主电影时间轴上,并且我们在shapes的时间轴上定义变量color。还是在shapes的时间轴上,我们放置一个名为triangle的电影剪辑实例。该电影的层次结构如下: img:\010205vs1
我们想要显示变量color(在shapes中)的值,但是要求代码附着在triangle的时间轴上。如何解决?方法一,我们可以使用一个绝对引用,如下: trace (_root.shape.color);
但是,这会使我们的代码依托于主电影时间轴。方法二,为使我们的代码更具灵活性,我们可以使用_parent属性创建一个相对引用,如下: trace (_parent.color);
方法一(使用_root)的工作原理是从顶向下的思维模式;它始于主时间轴并向下穿越电影剪辑层次直到触及变量color。方法二(使用_parent)的工作原理是从底向上的思维模式;它始于包含trace ()语句的电影剪辑(triangle),然后在电影剪辑层次中向上攀升一层并找到变量color。
我们可以在同一行中使用两次_parent,以在电影剪辑层次中向上攀升两层并访问到主时间轴上的变量size。下面,我们将一些代码附着在triangle上以引用主电影时间轴上的size: trace (_parent._parent.size);
连续两次使用_parent使我们向上提升了两层,在本例中,也就是说,将我们带到了电影的主时间轴上。
变量定位使用哪种方法取决于当你在不同的时间轴上放置电影剪辑符号的实例时你希望发生什么。在triangle例子中,如果我们在shapes电影剪辑中定义color的时侯希望对color的引用永远指向color,那么我们就应该使用_root语法,该语法为我们提供了对shapes中的color的一个固定的引用。但是,如果我们希望对color的引用指向某个变换了位置的color变量 —— 依赖于在哪个时间轴上有给定的triangle实例 —— 那么我们则应该使用_parent语法。这几句话好像不太容易理解,我希望你在继续之前,能够搞懂它们。为了帮助理解,我再提出一个设想:和上面的例子一样,假设我们在shapes中放置color变量,而shapes放在电影的主时间轴上,并且我们希望对该color变量的引用永远不要改变,那么对color使用绝对引用是合适的;变化一下,如果我们在电影的主时间轴上放置一个新的电影剪辑实例world,然后将shapes放置在world中以替代放置在主时间轴上,此时,上面的绝对引用出现了断点,因为shapes不再是主电影时间轴上的实例了,而是位于主电影时间轴上的world实例中。你看,_parent属性的优点现在体现出来了,尽管color变量相对于主时间轴的位置发生了变化(以前是:主时间轴 → shapes实例 → color变量;而现在是:主时间轴 → world实例 → shapes实例 → color变量。),但是使用_parent属性的相对引用却仍能指向它(_parent.color),这是因为color变量相对于triangle实例所在的时间轴的位置并未改变(以前是:color变量和triangle电影剪辑实例同处于shapes的时间轴上;而现在是:废话,还是同在shapes的时间轴上)。
6.2访问不同文档层上的变量 _root属性指向当前层(此层并非时间轴窗口上的层,而是指当前的电影文档在Flash播放器中的位置)的主电影时间轴。但是在Flash播放器的文档堆栈中能够容纳多个电影文档。顺便说一下,堆栈是计算机原理中的概念,涉及存储器和寄存器,有关堆栈的知识你可以在汇编语言中详细了解。载入播放器文档堆栈中的任何电影的主时间轴都被_leveln注明,n是该电影所在层的数字编号。层编号开始于0,象这样:_level0,_level1,_level2,_level3,等等。有关加载多个电影的知识我们在以后的章节中讨论。下面是一些展现多层变量定位的例子: _level1.firstName // firstName on level1's main timeline _level4.ball.area // area in ball clip on level4's main timeline _level0.guestBook.email // email in guestBook clip on level0's timeline
当使用点语句跨越多个电影剪辑实例定位变量时,必须确保在工作区中已经为你的电影剪辑实例命名,并且在代码中引用它们的时候必须正确输入它们的名字。如果你的实例没有被命名,即使代码中其他方面的语法构成是正确的,你的代码也不会正常工作。未命名的实例和拼写错误的实例名是最普遍的错误源。
7.电影剪辑变量的生存期 此前,我们说过变量的作用域反映了两个问题,(1)变量能存在多长时间?(2)在代码中的哪些地方能够设置并取回变量的值?
对于电影剪辑变量,我们已经知道了有关回答第二个问题的要素。但是我们跳过了对第一个问题的回答。现在,让我们回过头来面对它,并提出最后一个有关代码中变量的设想。
7.1设想五 假设我们创建有三个关键帧的新电影。在帧一上,我们放置一个电影剪辑实例,ball。在ball的时间轴上,我们创建一个变量,radius。然后我们右键点击帧二,在弹出菜单中选择插入关键帧,以使帧二上的内容和帧一中的完全一样。主时间轴上的帧三是空白关键帧(ball实例不会在那里出现)。
在主时间轴上的帧二,我们可以使用下列代码查明变量radius的值: trace (ball.radius); stop ();
现在,提出问题:在主时间轴上,如果我们将上面的代码从帧二移动到帧三,当电影测试时,Output窗口中会出现什么?
答案:什么也不会出现(或出现undefined)。因为电影剪辑实例ball并不在主时间轴的帧三上,因此ball中的所有变量对于此帧来说都是不可见的。
又一个问题,如果将上述代码从帧二向前移动到帧一而不是帧三,那么Output窗口又会出现什么呢?
答案:同样,什么也没有(或出现undefined)。为什么?ball出现在该帧中呀,怎么还是什么也没有呢?这是有关时间轴的问题,留点悬念,我们会在以后的章节中彻底地揭开谜底。
提示:只有当电影剪辑实例在工作区中呈现的时候,定义在电影剪辑时间轴上的电影剪辑变量才能持续(因为描述的是变量的生存期,所以我用持续这个词来表示可使用或可利用的意思)。定义在某个Flash文档的主时间轴上的变量在该文档中一直持续,除非该文档被从播放器中卸载(卸载可以通过unloadMovie ()函数完成,或者是因为另一个电影被加载到该电影所在的电影文档层上而发生的自动卸载)。
如果用脚本编辑某电影,而该电影中包含了一些电影剪辑,这些电影剪辑又被放置在不同时间轴的多个帧上,这时你会发现有关变量生存期的概念是多么重要。在你试图使用某个电影剪辑中的变量之前,一定要而且永远要确保该电影剪辑呈现在某个时间轴上。
8.局部变量 电影剪辑限制了电影剪辑变量的作用域,变量被定义在电影剪辑中,而且只要这些电影剪辑存在(在工作区中),那么这些变量就持续。
有时候,变量生存的时间比我们需要的还长。在我们只是需要一个临时变量的情况下,ActionScript提供了只有局部作用域的变量(也就是局部变量),局部变量生存的时间比常规的电影剪辑变量短了许多。
局部变量被用于函数中以及早期Flash 4风格的子程序中。如果你以前没有使用过函数或子程序,你可以跳过这一部分并在未来章节中学习了函数之后再重新光临这里。
函数经常使用一些在该函数外并不被需要的变量。例如,假设我们有一个函数,该函数显示某个被指定的数组中的所有元素: function displayElements (theArray) { var counter = 0; while (counter < theArray.length) { trace ("Element " + counter + ": " + theArray[counter]); counter ++; } }
变量counter对于显示该数组来说是必要的,但是除此之外,它再也没有存在的价值了。我们也可以将它定义在时间轴上,不过至少有两个原因能够说明那是很讨人嫌的行为:(1)如果counter持续,在电影播放的余下时间内它将占据着一定的存储器空间(虽然减价,但是内存条对我来说还是很贵),而且(2)如果counter在我们的函数外可以被访问,那么它可能会与其他的被命名为counter的变量发生冲突。因此,我们非常愿意看到在函数displayElements ()结束之后,counter立即死去。
为了促使counter在该函数的结尾处被自动删除,我们将它定义为局部变量。局部变量和电影剪辑变量不同,当定义它们的函数结束时,解释器会自动地将它们从存储器中移除(即取消分配,deallocated)。
为了将某个变量指定为局部的,从你的函数内部用var关键字来声明它,如同在前述displayElements ()函数的例子中那样。
提请注意,当位于某个函数的外部时,var语句创建一个常规的时间轴变量,而非局部变量。var语句所在的位置是产生这些区别的根源。
函数中的变量并非都是局部的。在某个函数的内部,通过忽略var关键字,我们可以创建并改变某个电影剪辑变量。即,如果我们不使用var关键字,而代之以仅仅为某个变量赋值,在某些情况下Flash认为该变量是非局部变量。考虑下列函数内部的变量赋值语句: function setHeight () { height = 10; }
语句height = 10;的作用取决于height是否是一个局部变量或者是一个时间轴变量。如果此前height被声明为局部变量,语句height = 10;则仅是简单地改变该局部变量的值。如果不存在名为height的局部变量,照此例中的样子,解释器创建一个名为height的电影剪辑变量(非局部变量),并将其值设置为10。作为非局部变量,即使是在该函数结束之后,height仍然持续。再次提醒注意:我多次使用时间轴变量和电影剪辑变量这两个术语,它们是同义词,我此前提到过。
下例示范局部和非局部变量的使用: var x = 5; // new nonlocal variable, x, is now 5 function variableDemo () { x = 10; // nonlocal variable, x, is now 10 y = 20; // new nonlocal variable, y, is now 20 var z = 30; // new local variable, z, is now 30 trace (x + "," + y + "," + z); // send variable values to Output window } variableDemo (); // call our function. displays: 10,20,30 trace (x); // display: 10 (reassignment in our function was permanent) trace (y); // display: 20 (nonlocal variable, y, still exists) trace (z); // display nothing or an "undefined" (local variable, z, has expired)
请注意,某个局部变量和某个非局部变量在同一脚本中共享同一个名字是可能的,但是它们有不同的作用域。不过,我必须得说这非常容易造成混乱并且是极其恶劣的编程行为。下面是这样的例子: var myColor = "blue"; function hexRed () { var myColor = "#FF0000"; return myColor; } trace (hexRed ()); // displays: #FF0000 (the local variable myColor) trace (myColor); // displays: blue (setting the local variable myColor to #FF0000 did not affect the nonlocal version) 转载请注明出处-零柒动力-07er.com |