Московский физико-технический институт
Опубликован: 23.12.2005 | Доступ: свободный | Студентов: 2868 / 252 | Оценка: 4.61 / 4.44 | Длительность: 27:18:00
ISBN: 978-5-9556-0051-2
Лекция 5:

Функции

Аннотация: Способы определения функций: заранее и "на лету". Реализация стражей включения при помощи последнего способа. Локальные переменные, локальные функции, иерархия областей видимости. Функция как объект, ссылки на функции, использование функции в качестве аргумента и в качестве возвращаемого значения. Конструирование функции в момент ее передачи в аргументы другой функции. Объект arguments и способы его использования; создание функций с произвольным числом аргументов. Методы arguments.caller и arguments.callee. Методы apply и call. Параметризованные функции. Принцип сохранения контекста и его применение: возвращение функции из другой функции, генератор функций. Эмуляция приватных полей.

То, как ActionScript реализует функции - на первый взгляд очевидно, на второй - непривычно, последующие же взгляды открывают ряд неожиданных возможностей (например, возможности параметризации). Но сначала мы должны научиться просто определять и вызывать функции.

Два способа определения функций

Определяем функцию заранее

Самый простой способ определить функцию - это написать

function func_name(arg1, arg2){
// здесь определяем тело функции
  return ret_value; // возвращаем значение
}

То есть почти как в С - только вместо типа возвращаемого значения мы пишем ключевое слово function. Кроме того, не указываются типы аргументов. Чтобы вернуть из функции значение, используем ключевое слово return. Если вслед за return сразу идет точка с запятой - получим аналог функции типа void из С, то есть никакое значение не возвращается. Наконец, можно вовсе не писать return. Словом, все как обычно.

Определяем функцию "на лету"

А вот второй способ определить функцию. Он хотя и выглядит несколько непривычно, зато предоставляет нам более широкие возможности.

func_name = function (arg1, arg2){
  // здесь определяем тело функции
  return ret_value; // возвращаем значение
}

То, что здесь написано, полностью эквивалентно первому определению. Но из этой новой записи мы можем сделать кое-какие выводы. Во-первых, похоже, что func_name - это что-то вроде ссылки на нашу функцию. Причем func_name является полем того клипа, в Actions которого мы определяем нашу функцию (так оно и есть - и для первого варианта определения, и для второго). Наконец, такое впечатление, что во втором случае ключевое слово function играет роль оператора генерации нужной нам функции. То есть вполне возможно, что этот "оператор" мы сможем использовать в любой удобный момент.

Определение "на лету" для реализации стражей включения

И действительно, в отличие от первого способа, второй можно применять внутри многих других конструкций языка, что предоставляет массу дополнительных возможностей использования. В частности, вы можете реализовать что-то вроде "условной трансляции" и " стражей включения ", которые применяются в заголовочных файлах на C++. На самом деле, поскольку инструкция #include работает в процессе компиляции *.swf-файла, то на любой код, стоящий вокруг нее, она внимания не обращает. "Зацикливание" включаемых файлов приводит к генерации синтаксической ошибки и избавиться от этого зацикливания мы сможем только путем перекомпоновки включаемых файлов, никакие "условные включения" здесь не помогут.

Однако есть другие задачи, для которых в С++ часто используются директивы препроцессора. Первая из них - чтобы все функции определялись ровно по разу. Во Флэше повторное определение к ошибкам никогда не приводит. Но нам может понадобиться переопределить какие-то функции "вручную". Нужно убедиться, что последующее включение подключаемого файла не заменит наше новое определение старым. Вот код для решения этой задачи. Во включаемом файле inc1.as (расширение as является сокращением от ActionScript) пишем

inc1_as = true;
_global.func = function(arg){
   //code
}

При подключении обрамляем оператор #include условным оператором. При этом подключение произойдет в любой случае, но выполнен этот код будет только если выполнено условие проверки.

if (!inc1_as){
   #include "inc1.as"
}

Вторая задача: иногда надо в зависимости от некоторого условия использовать тот или иной набор функций. Вот довольно очевидный код примера.

В файле inc1.as пишем:

_global.func = function(arg){
   //code1
}

В файле inc2.as пишем, в свою очередь:

_global.func = function(arg){
   //code2 - отличие здесь
}

И внутри нашего флэш-ролика поступаем так:

if (!advancedMode){
   #include "inc1.as"
}
else{
   #include "inc2.as"
}

В обоих случаях только второй способ определения функций нам подойдет. Кроме того, только этим способом мы можем поместить определяемые функции в объект _global. То есть нам не придется заново подключать файл с функциями в каждом клипе, что привело бы к лишним заботам и лишнему расходу памяти.

Пример необычного использования

Однако, говоря об использовании оператора генерации функции "во многих местах" мы имели в виду не только блоки внутри управляющих конструкций. Их действительно можно использовать в любом месте, где по смыслу может стоять оператор, возвращающий тип Function . Различным вариантам такого использования посвящена вся эта лекция. Чтобы вы сразу увидели, что мы понимаем под "необычным" (для С++ и Java) использованием, взгляните на такой код.

iter2 = function(f){
   return function(x){ return f(f(x)) }
}
trace(iter2(function(y){return y*y*y;})(2));

Этот код выводит число 512. Здесь есть два заслуживающих внимания момента: во-первых, мы написали функцию -генератор второй итерации переданного в нее аргумента. Во-вторых, этот аргумент мы создали "на лету", прямо внутри оператора вызова функции -генератора. Когда вы закончите читать эту лекцию, вы будете полностью понимать все детали подобных трюков.