русс | укр

Языки программирования

ПаскальСиАссемблерJavaMatlabPhpHtmlJavaScriptCSSC#DelphiТурбо Пролог

Компьютерные сетиСистемное программное обеспечениеИнформационные технологииПрограммирование

Все о программировании


Linux Unix Алгоритмические языки Аналоговые и гибридные вычислительные устройства Архитектура микроконтроллеров Введение в разработку распределенных информационных систем Введение в численные методы Дискретная математика Информационное обслуживание пользователей Информация и моделирование в управлении производством Компьютерная графика Математическое и компьютерное моделирование Моделирование Нейрокомпьютеры Проектирование программ диагностики компьютерных систем и сетей Проектирование системных программ Системы счисления Теория статистики Теория оптимизации Уроки AutoCAD 3D Уроки базы данных Access Уроки Orcad Цифровые автоматы Шпаргалки по компьютеру Шпаргалки по программированию Экспертные системы Элементы теории информации

Вложенные функции в качестве замыканий


Дата добавления: 2015-07-09; просмотров: 711; Нарушение авторских прав


 

Тот факт, что JavaScript допускает объявление вложенных функций, позволяет использовать функции как обычные данные и способствует организации взаи_ модействий между цепочками областей видимости, что позволяет получать ин_ тересные и мощные эффекты. Прежде чем приступить к описанию, рассмотрим функцию g, которая определяется внутри функции f. Когда вызывается функ_ ция f, ее цепочка областей видимости содержит объект вызова, за которым сле_ дует глобальный объект. Функция g определяется внутри функции f, таким об_ разом, цепочка областей видимости этой функции сохраняется как часть опре_ деления функции g. Когда вызывается функция g, ее цепочка областей видимо_ сти содержит уже три объекта: собственный объект вызова, объект вызова функции f и глобальный объект.


 

158 Глава 8. Функции

Порядок работы вложенных функций совершенно понятен, когда они вызыва_ ются в той же лексической области видимости, в которой определены. Напри_ мер, следующий фрагмент не содержит ничего необычного:

 

var x = "глобальная"; function f( ) {

 

var x = "локальная"; function g() { alert(x); } g();

}

f(); // При обращении к этой функции будет выведено слово "локальная"

 

Однако в JavaScript функции рассматриваются как обычные данные, поэтому их можно возвращать из других функций, присваивать свойствам объектов, со_ хранять в массивах и т. д. В этом нет ничего необычного до тех пор, пока на аре_ ну не выходят вложенные функции. Рассмотрим следующий фрагмент, где оп_ ределена функция, которая возвращает вложенную функцию. При каждом об_ ращении к ней она возвращает функцию. Сам JavaScript_код при этом не меня_ ется, но от вызова к вызову может изменяться область видимости, поскольку при каждом обращении к объемлющей функции могут изменяться ее аргумен_ ты. (То есть в цепочке областей видимости будет изменяться объект вызова объ_ емлющей функции.) Если попробовать сохранить возвращаемые функции в мас_ сиве и затем вызвать каждую из них, можно заметить, что они будут возвращать разные значения. Поскольку программный код функции при этом не изменяет_ ся и каждая из них вызывается в той же самой области видимости, единствен_ ное, чем можно объяснить разницу, – это различия между областями видимо_ сти, в которых функции были определены:



 

// Эта функция возвращает другую функцию

// От вызова к вызову изменяется область видимости,

 

// в которой была определена вложенная функция function makefunc(x) {

 

return function() { return x; }

}

 

// Вызвать makefunc() несколько раз и сохранить результаты в массиве: var a = [makefunc(0), makefunc(1), makefunc(2)];

 

// Теперь вызвать функции и вывести полученные от них значения.

// Хотя тело каждой функции остается неизменным, их области видимости

 

// изменяются, и при каждом вызове они возвращают разные значения: alert(a[0]( )); // Выведет 0

 

alert(a[1]( )); // Выведет 1 alert(a[2]( )); // Выведет 2

 

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


 

8.8. Область видимости функций и замыкания
   

 

является единственной ссылкой на объект вызова. Когда ссылка на объект уда_ ляется из цепочки, в дело вступает сборщик мусора.

 

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

 

Все меняется, если ссылка на вложенную функцию сохраняется в глобальной области видимости. Это происходит, когда вложенная функция передается в ви_ де возвращаемого значения объемлющей функции или сохраняется в виде свой_ ства какого_либо другого объекта. В этом случае появляется внешняя ссылка на вложенную функцию, при этом вложенная функция продолжает ссылаться на объект вызова объемлющей функции. В результате все объекты вызова, создан_ ные при каждом таком обращении к объемлющей функции, продолжают свое существование, а вместе с ними продолжают существование имена и значения аргументов функции и локальных переменных. JavaScript_программы не имеют возможности напрямую воздействовать на объект вызова, но его свойства явля_ ются частью цепочки областей видимости, создаваемой при любом обращении к вложенной функции. (Примечательно, что если объемлющая функция сохра_ нит глобальные ссылки на две вложенные функции, эти вложенные функции будут совместно использовать один и тот же объект вызова, а изменения, по_ явившиеся в результате обращения к одной из функций, будут видимы в другой.)

 

Функции в JavaScript представляют собой комбинацию исполняемого про_ граммного кода и области видимости, в которой этот код исполняется. Такая комбинация программного кода и области видимости в литературе по компью_ терной тематике называется замыканием (closure). Все JavaScript_функции яв_ ляются замыканиями. Однако все эти замыкания представляют интерес лишь в только что рассмотренной ситуации, когда вложенная функция экспортирует_ ся за пределы области видимости, в которой она была определена. Вложенные функции, используемые таким образом, нередко явно называют замыканиями.

 

Замыкания – это очень интересная и мощная техника программирования. Хотя замыкания используются довольно редко, они достойны того, чтобы изучить их. Если вы поймете механизм замыканий, вы без труда разберетесь в областях ви_ димости и без ложной скромности сможете назвать себя опытным программи_ стом на JavaScript.

 

Примеры замыканий

 

Иногда возникает необходимость, чтобы функция запоминала некоторое значе_ ние между вызовами. Значение не может сохраняться в локальной переменной, поскольку между обращениями к функции не сохраняется сам объект вызова. С ситуацией поможет справиться глобальная переменная, но это приводит к за_ хламлению пространства имен. В разделе 8.6.3 была представлена функция uniqueInteger(), которая задействует для этих целей собственное свойство. Одна_


 

160 Глава 8. Функции

ко можно пойти дальше и для создания частной (private) неисчезающей пере_ менной использовать замыкание. Вот пример такой функции, для начала без за_ мыкания:

 

// При каждом вызове возвращает разные значения uniqueID = function() {

 

if (!arguments.callee.id) arguments.callee.id = 0; return arguments.callee.id++;

};

 

Проблема заключается в том, что свойство uniqueID.id доступно за пределами функции и может быть установлено в значение 0, вследствие чего будет наруше_ но соглашение, по которому функция обязуется никогда не возвращать одно и то же значение дважды. Для решения этой проблемы можно сохранять значение в замыкании, доступ к которому будет иметь только эта функция:

 

uniqueID = (function() { // Значение сохраняется в объекте вызова функции var id = 0; // Это частная переменная, сохраняющая свое

 

// значение между вызовами функции

// Внешняя функция возвращает вложенную функцию, которая имеет доступ

// к этому значению. Эта вложенная функция сохраняется

// в переменной uniqueID выше.

 

return function() { return id++; }; // Вернуть и увеличить })(); // Вызов внешней функции после ее определения.

 

Пример 8.6 – это еще один пример замыкания. В нем демонстрируется, как част_ ные переменные, подобные той, что была показана ранее, могут совместно ис_ пользоваться несколькими функциями.

 

Пример 8.6. Создание частных свойств с помощью замыканий

// Эта функция добавляет методы доступа к свойству объекта "o"

// с заданными именами. Методы получают имена get<name>

// и set<name>. Если дополнительно предоставляется

// функция проверки, метод записи будет использовать ее

// для проверки значения перед сохранением. Если функция проверки

// возвращает false, метод записи генерирует исключение.

 

//

// Необычность такого подхода заключается в том, что значение

// свойства, доступного методам, сохраняется не в виде свойства

// объекта "o", а в виде локальной переменной этой функции.

// Кроме того, методы доступа определены локально, в этой функции

// и обеспечивают доступ к этой локальной переменной.

// Примечательно, что значение доступно только для этих двух методов

 

// и не может быть установлено или изменено иначе, как методом записи. function makeProperty(o, name, predicate) {

 

var value; // This is the property value

 

// Метод чтения просто возвращает значение. o["get" + name] = function() { return value; };

 

// Метод записи сохраняет значение или генерирует исключение,

 

// если функция проверки отвергает это значение.

o["set" + name] = function(v) {

if (predicate && !predicate(v))

throw "set" + name + ": неверное значение " + v;


 

8.8. Область видимости функций и замыкания
   

 

else

value = v;

};

}

 

// Следующий фрагмент демонстрирует работу метода makeProperty(). var o = {}; // Пустой объект

 

// Добавить методы доступа к свойству с именами getName() и setName()

// Обеспечить допустимость только строковых значений

makeProperty(o, "Name", function(x) { return typeof x == "string"; });

 

o.setName("Frank"); // Установить значение свойства
print(o.getName( )); // Получить значение свойства
o.setName(0); // Попытаться установить значение ошибочного типа

 

Самый практичный и наименее искусственный пример использования замыка_ ний, который мне известен, – это механизм точек останова, разработанный Сти_ вом Йеном (Steve Yen) и опубликованный на сайте http://trimpath.com как часть клиентской платформы TrimPath. Точка останова – это точка внутри функции, где останавливается исполнение программы, и разработчик получает возмож_ ность просмотреть значения переменных, вычислить выражения, вызвать функ_ ции и тому подобное. В механизме точек останова, придуманном Стивом, замы_ кания служат для хранения контекста исполнения текущей функции (включая локальные переменные и входные аргументы) и с помощью глобальной функ_ ции eval() позволяют просмотреть содержимое этого контекста. Функция eval() исполняет строки на языке JavaScript и возвращает полученные значения (по_ дробнее об этой функции можно прочитать в третьей части книги). Вот пример вложенной функции, которая работает как замыкание, выполняющее проверку своего контекста исполнения:

 

// Запомнить текущий контекст и позволить проверить его

// с помощью функции eval( )

var inspector = function($) { return eval($); }

 

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

 

Создать точку останова можно, передав это замыкание в функцию, как показано в примере 8.7.

 

Пример 8.7. Точки останова на основе замыканий

// Эта функция является реализацией точки останова. Она предлагает

// пользователю ввести выражение, вычисляет его с использованием

// замыкания и выводит результат. Используемое замыкание предоставляет

// доступ к проверяемой области видимости, таким образом любая функция

// будет создавать собственное замыкание.

//

// Реализовано по образу и подобию функции breakpoint() Стива Йена

// http://trimpath.com/project/wiki/TrimBreakpoint

 

function inspect(inspector, title) { var expression, result;

 

// Существует возможность отключать точки останова


 

162 Глава 8. Функции

 

// за счет создания свойства "ignore" у этой функции. if ("ignore" in arguments.callee) return;

 

while(true) {

 

// Определить, как вывести запрос перед пользователем var message = "";

 

// Если задан аргумент title, вывести его первым

if (title) message = title + "\n";

 

// Если выражение уже вычислено, вывести его вместе с его значением if (expression) message += "\n"+expression+" ==> "+result+"\n"; else expression = "";

 

// Типовое приглашение к вводу всегда должно выводиться:

message += "Введите выражение, которое следует вычислить:";

 

// Получить ввод пользователя, вывести приглашение и использовать

 

// последнее выражение как значение по умолчанию. expression = prompt(message, expression);

 

// Если пользователь ничего не ввел (или щелкнул на кнопке Отменить),

 

// работу в точке останова можно считать оконченной

// и вернуть управление.

if (!expression) return;

 

// В противном случае вычислить выражение с использованием

// замыкания в инспектируемом контексте исполнения.

// Результаты будут выведены на следующей итерации.

result = inspector(expression);

}

}

 

Обратите внимание: для вывода информации и ввода строки пользователя функ_ ция inspect() из примера 8.7 задействует метод Window.prompt() (подробнее об этом методе рассказывается в четвертой части книги).

 

Рассмотрим пример функции, вычисляющей факториал числа и использующей механизм точек останова:

 

function factorial(n) {

// Создать замыкание для этой функции

 

var inspector = function($) { return eval($); } inspect(inspector, "Вход в функцию factorial()");

 

var result = 1; while(n > 1) {

 

result = result * n; n__;

inspect(inspector, "factorial( ) loop");

}

 

inspect(inspector, "Выход из функции factorial()"); return result;

}

 



<== предыдущая лекция | следующая лекция ==>
Объект вызова как пространство имен | Замыкания и утечки памяти в Internet Explorer


Карта сайта Карта сайта укр


Уроки php mysql Программирование

Онлайн система счисления Калькулятор онлайн обычный Инженерный калькулятор онлайн Замена русских букв на английские для вебмастеров Замена русских букв на английские

Аппаратное и программное обеспечение Графика и компьютерная сфера Интегрированная геоинформационная система Интернет Компьютер Комплектующие компьютера Лекции Методы и средства измерений неэлектрических величин Обслуживание компьютерных и периферийных устройств Операционные системы Параллельное программирование Проектирование электронных средств Периферийные устройства Полезные ресурсы для программистов Программы для программистов Статьи для программистов Cтруктура и организация данных


 


Не нашли то, что искали? Google вам в помощь!

 
 

© life-prog.ru При использовании материалов прямая ссылка на сайт обязательна.

Генерация страницы за: 0.083 сек.