Funkcje
są to bloki kodu realizujące konkretne zadania i przystosowane do wielokrotnego wykorzystania w programie. W języku ActionScript 3.0 wyróżnia się dwa typy funkcji:
metody
i
zamknięcia funkcji
. To, czy funkcja jest nazywana metodą, czy zamknięciem funkcji, zależy od kontekstu, w jakim została zdefiniowana. Funkcja jest nazywana metodą, jeśli została zdefiniowana w ramach definicji klasy lub jest powiązana z instancją obiektu. Funkcja jest nazywana zamknięciem funkcji, jeśli jest zdefiniowana w inny sposób.
Funkcje były zawsze bardzo ważnym elementem języka ActionScript. Na przykład w języku ActionScript 1.0 nie istniało słowo
class
, dlatego „klasy” definiowało się za pomocą funkcji-konstruktorów. Mimo że od tamtej pory do języka dodano słowo
class
, pełne zrozumienie istoty funkcji nadal jest ważne dla programistów, którzy chcieliby w pełni wykorzystać potencjał języka. Początkowo może to stanowić pewien problem dla programistów, którzy oczekują, że funkcje w języku ActionScript będą zachowywały się podobnie, jak w języku C++ lub Java. Mimo że podstawowe zasady definiowania i wywoływania funkcji nie powinny sprawiać trudności doświadczonym programistom, pewne bardziej zaawansowane cechy funkcji języka ActionScript wymagają bliższego wyjaśnienia.
Podstawowe pojęcia związane z funkcjami
Wywoływanie funkcji
Wywołanie funkcji ma postać jej identyfikatora z dodanymi nawiasami (
()
). Operator nawiasów obejmuje wszelkie parametry, które chcemy przekazać do funkcji. Na przykład: funkcja
trace()
jest funkcją najwyższego poziomu języka ActionScript 3.0:
trace("Use trace to help debug your script");
W przypadku wywołania funkcji bez parametrów należy za identyfikatorem funkcji umieścić pustą parę nawiasów. Na przykład do wygenerowania liczby losowej możemy użyć funkcji
Math.random()
, która nie ma parametrów:
var randomNum:Number = Math.random();
Definiowanie własnych funkcji
W języku ActionScript 3.0 istnieją dwa sposoby definiowania funkcji: użycie instrukcji funkcyjnej lub wyrażenia funkcyjnego. To, którą technikę wybierzemy, zależy od preferowanego stylu programowania: bardziej statycznego lub bardziej dynamicznego. Programiści, którzy preferują styl statyczny, ścisły, definiują funkcje za pomocą instrukcji funkcyjnych. Użycie wyrażeń funkcyjnych do definiowania funkcji jest uzasadnione specyficznymi potrzebami. Wyrażenia funkcyjne są częściej używane w stylu dynamicznym, w standardowym trybie kompilacji.
Instrukcje funkcyjne
Instrukcje funkcyjne to preferowana technika definiowania funkcji w trybie ścisłym. Instrukcja funkcyjna rozpoczyna się od słowa kluczowego
function
, po którym następują:
-
nazwa funkcji;
-
lista parametrów oddzielonych przecinkami, ujęta w nawiasy;
-
treść funkcji — tzn. kod w języku ActionScript, który ma być wykonywany po wywołaniu funkcji — ujęta w nawiasy sześcienne.
Na przykład poniższy kod tworzy funkcję, w której zdefiniowany jest parametr. Następnie wywołuje funkcję, przekazując jako wartość parametru ciąg znaków “
hello"
:
function traceParameter(aParam:String)
{
trace(aParam);
}
traceParameter("hello"); // hello
Wyrażenia funkcyjne
Drugi sposób deklarowania funkcji polega na użyciu instrukcji przypisania z wyrażeniem funkcyjnym, które czasami bywa nazywane literałem funkcyjnym lub funkcją anonimową. Jest to bardziej rozwlekła metoda zapisu, szeroko stosowana we wcześniejszych wersjach języka ActionScript.
Instrukcja przypisania z wyrażeniem funkcyjnym rozpoczyna się od słowa kluczowego
var
, po którym następują:
-
nazwa funkcji;
-
operator dwukropka (
:
);
-
klasa
Function
wskazująca typ danych;
-
operator przypisania (
=
);
-
słowo kluczowe
function
;
-
lista parametrów oddzielonych przecinkami, ujęta w nawiasy;
-
treść funkcji — tzn. kod w języku ActionScript, który ma być wykonywany po wywołaniu funkcji — ujęta w nawiasy sześcienne.
W poniższym przykładzie zadeklarowano funkcję
traceParameter
, korzystając z wyrażenia funkcyjnego:
var traceParameter:Function = function (aParam:String)
{
trace(aParam);
};
traceParameter("hello"); // hello
Należy zwrócić uwagę, że w tym przypadku nie określamy nazwy funkcji, tak jak miało to miejsce w instrukcji funkcyjnej. Inna ważna różnica między instrukcjami funkcyjnymi a wyrażeniami funkcyjnymi polega na tym, że wyrażenie funkcyjne jest wyrażeniem, a nie instrukcją. Oznacza to, że wyrażenie funkcyjne nie może występować w kodzie autonomicznie, natomiast instrukcja funkcyjna — może. Wyrażenie funkcyjne może być użyte tylko w ramach instrukcji, zwykle instrukcji przypisania. Poniższy przykład ilustruje wyrażenie funkcyjne przypisane do elementu tablicy:
var traceArray:Array = new Array();
traceArray[0] = function (aParam:String)
{
trace(aParam);
};
traceArray[0]("hello");
Wybór między instrukcjami a wyrażeniami
Co do zasady należy używać instrukcji funkcyjnych, chyba że szczególne okoliczności nakazują użycie wyrażenia. Instrukcje funkcyjne są bardziej zwięzłe i zachowują się podobnie w trybie ścisłym i w trybie standardowym, co odróżnia je od wyrażeń funkcyjnych.
Instrukcje funkcyjne są bardziej czytelne niż instrukcje przypisania zawierające wyrażenia funkcyjne. Instrukcje funkcyjne sprawiają, że kod jest bardziej zwięzły; są bardziej jednoznaczne niż wyrażenia funkcyjne, które wymagają zastosowania zarówno słowa kluczowego
var
, jak i
function
.
Instrukcje funkcyjne są traktowane podobnie w obu trybach kompilatora, tzn. zarówno w trybie ścisłym, jak i standardowym, dozwolone jest używanie kropki do wywoływania metod zadeklarowanych za pomocą instrukcji funkcyjnych. Nie zawsze dotyczy to metod zadeklarowanych za pomocą metod funkcyjnych. W poniższym przykładzie zadeklarowano klasę Example z dwiema metodami:
methodExpression()
, która jest zadeklarowana za pomocą wyrażenia funkcyjnego, oraz
methodStatement()
, która jest zadeklarowana za pomocą instrukcji funkcyjnej. W trybie ścisłym nie jest dozwolone użycie składni z kropką do wywołania metody
methodExpression()
.
class Example
{
var methodExpression = function() {}
function methodStatement() {}
}
var myEx:Example = new Example();
myEx.methodExpression(); // error in strict mode; okay in standard mode
myEx.methodStatement(); // okay in strict and standard modes
Wyrażenia funkcyjne uważa się za lepiej przystosowane do programowania zorientowanego na zachowanie dynamiczne w czasie wykonywania. Programista, który preferuje pracę w trybie ścisłym, ale musi wywołać metodę zadeklarowaną za pomocą wyrażenia funkcyjnego, ma do wyboru dwie techniki. Po pierwsze, można wywołać metodę, używając nawiasów kwadratowych (
[]
) zamiast kropki (
.
). Poniższe wywołanie metody zostanie pomyślnie skompilowane zarówno w trybie ścisłym, jak i standardowym:
myExample["methodLiteral"]();
Po drugie — można zadeklarować całą klasę jako dynamiczną. Mimo że umożliwi to wywoływanie metody za pomocą operatora kropki, wadą takiego rozwiązania jest rezygnacja z części funkcjonalności trybu ścisłego w odniesieniu do wszystkich instancji tej klasy. Kompilator nie zgłosi na przykład błędu, gdy spróbujemy uzyskać dostęp do niezdefiniowanej właściwości w instancji klasy dynamicznej.
Istnieją pewne okoliczności, w których wyrażenia funkcyjne okazują się użyteczne. Jednym z typowych zastosowań wyrażeń funkcyjnych są funkcje używane tylko raz, a następnie likwidowane. Rzadziej spotykane zastosowanie polega na powiązaniu funkcji z właściwością prototypową. Więcej informacji zawiera sekcja
Obiekt prototypowy
.
Istnieją także dwie subtelne różnice między instrukcjami funkcyjnymi a wyrażeniami funkcyjnymi, które należy wziąć pod uwagę, dokonując wyboru techniki. Pierwsza różnica polega na tym, że wyrażenia funkcyjne nie istnieją jako odrębne obiekty z perspektywy zarządzania pamięcią i czyszczenia pamięci. Innymi słowy, gdy przypisujemy wyrażenie funkcyjne do innego obiektu, np. elementu tablicy lub właściwości obiektu, tworzymy jedyne odwołanie do tego wyrażenia funkcyjnego, jakie istnieje w kodzie. Jeśli tablica lub obiekt, z którym powiązane jest wyrażenie, znajdzie się poza zasięgiem lub z innego powodu przestanie być dostępne, utracimy dostęp do wyrażenia funkcyjnego. Usunięcie tablicy lub obiektu spowoduje, że pamięć zajęta przez wyrażenie funkcyjne zostanie oznaczona jako przeznaczona do wyczyszczenia, a w konsekwencji będzie ją można odzyskać i wykorzystać w innym celu.
Poniższy przykład demonstruje fakt, że po usunięciu właściwości, do której przypisane jest wyrażenie funkcyjne, funkcja przestaje być dostępna. Klasa Test jest klasą dynamiczną, a zatem możemy do niej dodać właściwość o nazwie
functionExp
zawierającą wyrażenie funkcyjne. Funkcję
functionExp()
można wywoływać, posługując się kropką, jednak po usunięciu właściwości
functionExp
funkcja ta przestaje być dostępna.
dynamic class Test {}
var myTest:Test = new Test();
// function expression
myTest.functionExp = function () { trace("Function expression") };
myTest.functionExp(); // Function expression
delete myTest.functionExp;
myTest.functionExp(); // error
Jeśli natomiast funkcja zostanie najpierw zdefiniowana za pomocą instrukcji funkcyjnej, to będzie istnieć jako autonomiczny obiekt, nawet po usunięciu właściwości, do której została przypisana. Operator
delete
działa tylko na właściwościach obiektów, a zatem nawet jawne usunięcie funkcji
stateFunc()
nie odniesie skutku.
dynamic class Test {}
var myTest:Test = new Test();
// function statement
function stateFunc() { trace("Function statement") }
myTest.statement = stateFunc;
myTest.statement(); // Function statement
delete myTest.statement;
delete stateFunc; // no effect
stateFunc();// Function statement
myTest.statement(); // error
Druga różnica między instrukcjami funkcyjnymi a wyrażeniami funkcyjnymi polega na tym, że instrukcje funkcyjne istnieją w całym zasięgu, w jakim są zdefiniowane, również we wszystkich instrukcjach poprzedzających taką instrukcję funkcyjną. Z kolei wyrażenia funkcyjne są zdefiniowane tylko w instrukcjach następujących po nich. Na przykład poniższy kod pomyślnie wywołuje funkcję
scopeTest()
zanim została ona zdefiniowana:
statementTest(); // statementTest
function statementTest():void
{
trace("statementTest");
}
Wyrażenia funkcyjne są niedostępne, zanim nie zostaną zdefiniowane, a zatem następujący kod spowoduje zgłoszenie błędu w czasie wykonywania:
expressionTest(); // run-time error
var expressionTest:Function = function ()
{
trace("expressionTest");
}
Zwracanie wartości z funkcji
W celu zwrócenia wartości z funkcji należy użyć instrukcji
return
, po której powinno następować wyrażenie lub wartość literalna przeznaczona do zwrócenia. Poniższy kod przykładowy zwraca wyrażenie reprezentujące parametr:
function doubleNum(baseNum:int):int
{
return (baseNum * 2);
}
Należy zwrócić uwagę, że instrukcja
return
kończy wykonywanie funkcji, a zatem instrukcje następujące po instrukcji
return
nie zostaną wykonane, co ilustruje poniższy przykład:
function doubleNum(baseNum:int):int {
return (baseNum * 2);
trace("after return"); // This trace statement will not be executed.
}
W trybie ścisłym, jeśli zadeklarowano typ wartości zwracanej przez funkcję, to funkcja musi zwracać wartość odpowiedniego typu. Na przykład poniższy kod spowoduje zgłoszenie błędu w trybie ścisłym, ponieważ nie zwraca właściwej wartości:
function doubleNum(baseNum:int):int
{
trace("after return");
}
Funkcje zagnieżdżone
Istnieje możliwość zagnieżdżania funkcji, tzn. deklarowania funkcji wewnątrz innych funkcji. Funkcja zagnieżdżona jest dostępna tylko wewnątrz swojej funkcji nadrzędnej, chyba że odwołanie do funkcji zostanie przekazane do kodu zewnętrznego. W poniższym przykładowym kodzie zadeklarowano dwie funkcje zagnieżdżone wewnątrz funkcji
getNameAndVersion()
:
function getNameAndVersion():String
{
function getVersion():String
{
return "10";
}
function getProductName():String
{
return "Flash Player";
}
return (getProductName() + " " + getVersion());
}
trace(getNameAndVersion()); // Flash Player 10
Funkcje zagnieżdżone są przekazywane do kodu zewnętrznego jako zamknięcia funkcji, tzn. zachowują wszelkie definicje pozostające w zasięgu, w którym funkcja była zdefiniowana. Więcej informacji zawiera sekcja
Zasięg funkcji
.
Parametry funkcji
Język ActionScript 3.0 oferuje w odniesieniu do parametrów funkcji pewne rozwiązania, które mogą stanowić nowość dla programistów, którzy dotychczas nie zetknęli się z tym językiem. Mimo że koncepcja przekazywania parametrów przez wartość lub przez odwołanie (referencję) powinna być znana większości programistów, to wielu czytelników prawdopodobnie nie zetknęło się wcześniej z obiektem
arguments
oraz parametrem ... (rest).
Przekazywanie argumentów przez wartość lub przez odwołanie
W wielu językach programowania bardzo istotne jest rozróżnienie między przekazywaniem argumentów przez wartość a przekazywaniem ich przez odwołanie. Różnica ta wpływa na konstrukcję kodu.
Przekazanie przez wartość oznacza, że wartość argumentu jest kopiowana do zmiennej lokalnej używanej wewnątrz funkcji. Przekazanie przez odwołanie oznacza przekazanie wyłącznie odwołania (wskaźnika) do argumentu, a nie jego faktycznej wartości. Argument nie jest kopiowany. Tworzone jest natomiast odwołanie do zmiennej przekazanej jako argument i odwołanie to jest przypisywane zmiennej lokalnej używanej wewnątrz funkcji. Jako odwołanie do zmiennej znajdującej się poza funkcją, zmienna lokalna umożliwia zmianę wartości pierwotnej zmiennej.
W języku ActionScript 3.0 wszystkie argumenty są przekazywane przez odwołanie, ponieważ wszystkie wartości są przechowywane jako obiekty. Jednak obiekty należące do pierwotnych typów danych, czyli Boolean, Number, int, uint oraz String, mają specjalne operatory sprawiające, że zachowują się, jak gdyby były przekazywane przez wartość. Poniższy przykładowy kod tworzy funkcję o nazwie
passPrimitives()
, która definiuje dwa parametry o nazwach
xParam
i
yParam
, oba typu int. Parametry te są podobne do zmiennych lokalnych zadeklarowanych w treści funkcji
passPrimitives()
. Gdy funkcja zostaje wywołana z argumentami
xValue
i
yValue
, parametry
xParam
i
yParam
są inicjowane odwołaniami do obiektów int reprezentowanych przez argumenty
xValue
i
yValue
. Ponieważ argumenty należą do typu pierwotnego, zachowują się tak, jak gdyby były przekazywane przez wartość. Mimo że parametry
xParam
i
yParam
początkowo zawierały tylko odwołania do obiektów
xValue
i
yValue
, wszelkie modyfikacje zmiennych w treści funkcji powodują utworzenie nowych kopii wartości w pamięci.
function passPrimitives(xParam:int, yParam:int):void
{
xParam++;
yParam++;
trace(xParam, yParam);
}
var xValue:int = 10;
var yValue:int = 15;
trace(xValue, yValue);// 10 15
passPrimitives(xValue, yValue); // 11 16
trace(xValue, yValue);// 10 15
Wewnątrz funkcji
passPrimitives()
wartości
xParam
i
yParam
są inkrementowane, ale nie wpływa to na wartość
xValue
i
yValue
, czego dowodzi wynik ostatniej instrukcji
trace
. Kod działałby tak samo nawet wtedy, gdyby parametry miały nazwy identyczne z nazwami zmiennych
xValue
i
yValue
, ponieważ nazwy
xValue
i
yValue
wewnątrz funkcji wskazywałyby na nowe miejsca w pamięci, autonomiczne względem zmiennych o tych samych nazwach poza funkcją.
Wszystkie pozostałe obiekty — tj. obiekty nienależące do pierwotnych typów danych — są zawsze przekazywane przez odwołanie, co umożliwia zmianę wartości oryginalnej zmiennej. Poniższy przykładowy kod tworzy obiekt o nazwie
objVar
z dwiema właściwościami:
x
i
y
. Obiekt ten jest przekazywany jako argument do funkcji
passByRef()
. Ponieważ obiekt nie należy do typu pierwotnego, jest nie tylko przekazywany przez odwołanie, lecz również pozostaje odwołaniem. Oznacza to, że modyfikacja parametrów wewnątrz funkcji wpłynie na właściwości obiektów poza funkcją.
function passByRef(objParam:Object):void
{
objParam.x++;
objParam.y++;
trace(objParam.x, objParam.y);
}
var objVar:Object = {x:10, y:15};
trace(objVar.x, objVar.y); // 10 15
passByRef(objVar); // 11 16
trace(objVar.x, objVar.y); // 11 16
Parametr
objParam
odwołuje się do tego samego obiektu, co zmienna globalna
objVar
. Jak wynika z wyników instrukcji
trace
w przykładowym kodzie, zmiany właściwości
x
i
y
obiektu
objParam
są odzwierciedlone w obiekcie
objVar
.
Domyślne wartości parametrów
W języku ActionScript 3.0 można zadeklarować
domyślne właściwości parametrów
dla funkcji. Jeśli w wywołaniu funkcji mającej wartości domyślne parametrów zostaną pominięte takie parametry, przyjmowane są wartości podane w definicji funkcji. Wszystkie parametry z wartościami domyślnymi muszą być umieszczone na końcu listy parametrów. Przypisywane wartości domyślne muszą być stałymi znanymi w czasie kompilacji. Istnienie wartości domyślnej parametru powoduje, że parametr ten automatycznie staje się
parametrem opcjonalnym
. Parametr bez wartości jest uznawany za
parametr wymagany
.
Poniższy przykładowy kod tworzy funkcję z trzema parametrami, z których dwa mają wartości domyślne. Gdy funkcja zostanie wywołana tylko z jednym parametrem, dla pozostałych dwóch parametrów przyjęte zostaną wartości domyślne.
function defaultValues(x:int, y:int = 3, z:int = 5):void
{
trace(x, y, z);
}
defaultValues(1); // 1 3 5
Obiekt arguments
Wewnątrz funkcji, do której przekazywane są parametry, można skorzystać z obiektu
arguments
, aby uzyskać dostęp do informacji o przekazanych parametrach. Oto niektóre ważne aspekty użycia obiektu
arguments
:
-
Obiekt
arguments
jest tablicą zawierającą wszystkie parametry przekazane do funkcji.
-
Właściwość
arguments.length
zawiera liczbę argumentów przekazanych do funkcji.
-
Właściwość
arguments.callee
zawiera odwołanie do samej funkcji, co bywa przydatne przy rekurencyjnych wywołaniach wyrażeń funkcyjnych.
Uwaga:
Obiekt
arguments
nie jest dostępny, jeśli którykolwiek z parametrów ma nazwę
arguments
lub jeśli używany jest parametr ... (rest).
Jeśli do obiektu
arguments
istnieją odwołania w treści funkcji, wówczas w języku ActionScript 3.0 dozwolone jest wywoływanie funkcji z większą liczbą parametrów niż podana w definicji funkcji. Jednak w trybie dokładnym kompilator zgłosi błąd, jeśli liczba parametrów nie będzie zgodna z liczbą parametrów wymaganych (i ewentualnie parametrów opcjonalnych). Korzystając z obiektu
arguments
jak z tablicy, można odwołać się do dowolnego parametru przekazanego do funkcji, niezależnie od tego, czy został wymieniony w definicji funkcji. Poniższy przykład, który daje się skompilować bezbłędnie tylko w trybie standardowym, ilustruje użycie tablicy
arguments
oraz właściwości
arguments.length
do wyświetlenia wszystkich parametrów przekazanych do funkcji
traceArgArray()
:
function traceArgArray(x:int):void
{
for (var i:uint = 0; i < arguments.length; i++)
{
trace(arguments[i]);
}
}
traceArgArray(1, 2, 3);
// output:
// 1
// 2
// 3
Właściwość
arguments.callee
jest często używana w funkcjach anonimowych do uzyskania rekurencji. Takie rozwiązanie pozwala na bardziej elastyczne programowanie. Jeśli w toku prac nad programem nazwa funkcji rekurencyjnej ulegnie zmianie, nie będzie konieczne modyfikowanie wywołania rekurencyjnego w treści funkcji — wystarczy zamiast nazwy użyć odwołania
arguments.callee
. Właściwość
arguments.callee
została użyta w poniższym wyrażeniu funkcyjnym, umożliwiając rekurencję:
var factorial:Function = function (x:uint)
{
if(x == 0)
{
return 1;
}
else
{
return (x * arguments.callee(x - 1));
}
}
trace(factorial(5)); // 120
Jeśli deklaracja funkcji zawiera parametr ... (rest), to obiekt
arguments
nie jest dostępny. Do parametrów należy się wówczas odwoływać za pomocą nazw, pod jakimi zostały zadeklarowane.
Należy również unikać używania ciągu znaków
"arguments"
w charakterze nazwy parametru, ponieważ taki parametr przesłoni obiekt
arguments
. Przykładowo, jeśli funkcja
traceArgArray()
zostanie zmodyfikowana poprzez dodanie parametru
arguments
, odwołania do obiektu
arguments
w treści funkcji będą teraz dotyczyły tego parametru, a nie obiektu
arguments
. Poniższy kod nie spowoduje wyświetlenia żadnych wyników.
function traceArgArray(x:int, arguments:int):void
{
for (var i:uint = 0; i < arguments.length; i++)
{
trace(arguments[i]);
}
}
traceArgArray(1, 2, 3);
// no output
Obiekt
arguments
w poprzednich wersjach języka ActionScript zawierał też właściwość o nazwie
caller
będącą odwołaniem do funkcji, która wywołała bieżącą funkcję. Właściwość
caller
nie występuje w języku ActionScript 3.0, ale jeśli musimy odwołać się do funkcji wywołującej, możemy ją tak zmodyfikować, aby przekazywała odwołanie do samej siebie jako dodatkowy parametr.
Parametr ... (rest)
W języku ActionScript 3.0 wprowadzono możliwość zadeklarowania nowego rodzaju parametru o nazwie ... (rest). Umożliwia on podanie jako argumentu jednej tablicy zawierającej dowolną liczbę argumentów oddzielonych przecinkami. Parametr może mieć dowolną nazwę niebędącą słowem zastrzeżonym. Deklaracja takiego parametru musi być ostatnia na liście deklaracji. Użycie tego parametru powoduje, że obiekt
arguments
staje się niedostępny. Mimo że parametr... (rest) oferuje tę samą funkcjonalność, co tablica
arguments
i właściwość
arguments.length
, nie oferuje funkcjonalności właściwości
arguments.callee
. Decydując się na użycie parametru ... (rest), należy mieć świadomość, że uniemożliwi to korzystanie z właściwości
arguments.callee
.
W poniższym przykładzie funkcja
traceArgArray()
została zmodyfikowana w taki sposób, że zamiast obiektu
arguments
używany jest w niej parametr ... (rest):
function traceArgArray(... args):void
{
for (var i:uint = 0; i < args.length; i++)
{
trace(args[i]);
}
}
traceArgArray(1, 2, 3);
// output:
// 1
// 2
// 3
Parametru ... (rest) można również używać z innymi parametrami, o ile będzie on ostatni na liście parametrów. W poniższym przykładzie funkcja
traceArgArray()
została zmodyfikowana w taki sposób, że pierwszy parametr,
x
, jest typu int, a drugi parametr jest parametrem ... (rest). Wyniki nie zawierają pierwszej wartości, ponieważ pierwszy parametr nie należy już do tablicy utworzonej przez parametr ... (rest).
function traceArgArray(x: int, ... args)
{
for (var i:uint = 0; i < args.length; i++)
{
trace(args[i]);
}
}
traceArgArray(1, 2, 3);
// output:
// 2
// 3
Funkcje jako obiekty
W języku ActionScript 3.0 funkcje są obiektami. Utworzenie funkcji jest równoznaczne z utworzeniem obiektu, który nie tylko można przekazywać jako parametr do innej funkcji, lecz który może mieć również własne właściwości i metody.
Funkcje jako argumenty innych funkcji są przekazywane przez odwołanie, a nie przez wartość. Przekazując funkcję jako argument, podajemy tylko identyfikator, bez nawiasów używanych w wywołaniu metody. W poniższym przykładowym kodzie funkcja o nazwie
clickListener()
jest przekazywana jako argument do metody
addEventListener()
:
addEventListener(MouseEvent.CLICK, clickListener);
Mimo że programistom, którzy po raz pierwszy stykają się z językiem ActionScript, często wydaje się to dziwne, funkcje mogą mieć właściwości i metody — tak samo, jak każdy inny obiekt. W istocie każda funkcja ma właściwość o nazwie
length
przeznaczoną tylko do odczytu, w której przechowywana jest liczba parametrów zdefiniowanych dla funkcji. Należy odróżnić tę właściwość od właściwości
arguments.length
, która zawiera liczbę argumentów faktycznie przekazanych do funkcji. Jak pamiętamy, w języku ActionScript liczba argumentów przekazanych do funkcji może być większa od liczby parametrów zdefiniowanych dla tej funkcji. Poniższy przykład, który można skompilować bezbłędnie tylko w trybie standardowym (gdyż w trybie ścisłym wymagana jest dokładna zgodność liczby przekazanych argumentów z liczbą zdefiniowanych parametrów), ilustruje różnicę między tymi dwiema właściwościami:
// Compiles only in standard mode
function traceLength(x:uint, y:uint):void
{
trace("arguments received: " + arguments.length);
trace("arguments expected: " + traceLength.length);
}
traceLength(3, 5, 7, 11);
/* output:
arguments received: 4
arguments expected: 2 */
W trybie standardowym możliwe jest definiowanie własnych właściwości funkcji poza treścią funkcji. Właściwości funkcji mogą pełnić rolę właściwości quasi-statycznych, pozwalając na zapisanie stanu zmiennej związanej z funkcją. Na przykład można w ten sposób rejestrować liczbę wywołań funkcji. Taka funkcjonalność byłaby przydatna w grze, w której chcemy rejestrować każde użycie określonego polecenia przez użytkownika. Jednak ten sam efekt uzyskalibyśmy, stosując właściwość klasy statycznej. Poniższy kod przykładowy, który da się skompilować bez błędów tylko w trybie standardowym (ponieważ tryb ścisły nie zezwala na dodawanie właściwości dynamicznych do funkcji) tworzy właściwość funkcji poza jej deklaracją i inkrementuje tę właściwość przy każdym wywołaniu funkcji:
// Compiles only in standard mode
var someFunction:Function = function ():void
{
someFunction.counter++;
}
someFunction.counter = 0;
someFunction();
someFunction();
trace(someFunction.counter); // 2
Zasięg funkcji
Zasięg funkcji określa nie tylko to, w których miejscach kodu funkcję można wywoływać, lecz także do jakich definicji funkcja ma dostęp. Identyfikatory funkcji podlegają tym samym regułom zasięgu, co identyfikatory zmiennych. Funkcja zadeklarowana w zasięgu globalnym jest dostępna w całym kodzie. Na przykład w języku ActionScript 3.0 zdefiniowane są funkcje globalne, takie jak
isNaN()
i
parseInt()
, z których można korzystać w dowolnym miejscu kodu. Funkcja zagnieżdżona — czyli funkcja zadeklarowana wewnątrz innej funkcji — może być używana w dowolnym miejscu funkcji, w którym została zadeklarowana.
Łańcuch zasięgów
Za każdym razem, gdy rozpoczyna się wykonywanie funkcji, tworzona jest pewna liczba obiektów i właściwości. Po pierwsze, tworzony jest specjalny obiekt nazywany
obiektem aktywacji
, w którym przechowywane są parametry i wszelkie zmienne lokalne lub funkcje zadeklarowane w treści funkcji. Obiekt aktywacji nie jest dostępny bezpośrednio, ponieważ stanowi mechanizm wewnętrzny. Po drugie: tworzony jest
łańcuch zasięgów
zawierający uporządkowaną listę obiektów, w których poszukiwane są deklaracje identyfikatorów. Każda wykonywana funkcja ma swój łańcuch zasięgu przechowywany we właściwości wewnętrznej. W przypadku funkcji zagnieżdżonych łańcuch zasięgu rozpoczyna się od jej własnego obiektu aktywacji, po którym następuje obiekt aktywacji funkcji nadrzędnej. Łańcuch jest kontynuowany na tej zasadzie, aż do osiągnięcia obiektu globalnego. Obiekt globalny jest tworzony z chwilą rozpoczęcia wykonywania programu w języku ActionScript i zawiera wszystkie zmienne i funkcje globalne.
Zamknięcia funkcji
Zamknięcie funkcji
to obiekt zawierający obraz stanu funkcji i jej
środowiska leksykalnego
. Środowisko leksykalne funkcji zawiera wszystkie zmienne, właściwości, metody i obiekty w łańcuchu zasięgów funkcji oraz ich wartości. Zamknięcia funkcji tworzone są za każdym razem, gdy funkcja jest wykonywana w oderwaniu od obiektu lub klasy. Fakt, że zamknięcia funkcji zachowują zasięg, w którym zostały zdefiniowane, prowadzi do interesujących skutków przekazania funkcji jako argumentu lub wartości zwracanej do innego zasięgu.
Przykładowo, w poniższym kodzie tworzone są dwie funkcje:
foo()
, która zwraca funkcję zagnieżdżoną o nazwie
rectArea()
, obliczającą pole powierzchni prostokąta, oraz
bar()
, która wywołuje funkcję
foo()
i zapisuje zwrócone zamknięcie funkcji w zmiennej o nazwie
myProduct
. Mimo że funkcja
bar()
definiuje własną zmienną lokalną
x
(o wartości 2), w wywołanym zamknięciu funkcji
myProduct()
zachowana jest zmienna
x
(o wartości 40) zdefiniowana w funkcji
foo().
Funkcja
bar()
zwraca zatem wartość
160
zamiast wartości
8
.
function foo():Function
{
var x:int = 40;
function rectArea(y:int):int // function closure defined
{
return x * y
}
return rectArea;
}
function bar():void
{
var x:int = 2;
var y:int = 4;
var myProduct:Function = foo();
trace(myProduct(4)); // function closure called
}
bar(); // 160
Metody zachowują się podobnie, tj. zachowują informacje o środowisku leksykalnym, w którym zostały utworzone. Ta cecha jest najbardziej zauważalna, gdy metoda zostanie wyodrębniona ze swojej instancji, stając się metodą powiązaną. Zasadniczą różnicą między zamknięciem funkcji a metodą powiązaną jest fakt, że wartość słowa kluczowego
this
w metodzie powiązanej jest zawsze odwołaniem do instancji, do której ta metoda pierwotnie należała, natomiast w zamknięciu funkcji wartość słowa kluczowego
this
może ulegać zmianie.
|
|
|