JavaScript. Подробное руководство, 6-е издание
Шрифт:
8.6. Замыкания
Как и в большинстве языков программирования,
Технически все функции в языке JavaScript образуют замыкания: они являются объектами и имеют ассоциированные с ними цепочки областей видимости. Большинство функций вызываются внутри той же цепочки областей видимости, которая действовала на момент определения функции, и в этой ситуации факт образования замыкания не имеет никакого значения. Интересные особенности замыканий начинают проявляться, когда их вызов производится в другой цепочке областей видимости, отличной от той, что действовала на момент определения. Чаще всего это происходит, когда объект вложенной функции возвращается функцией, вмещающей ее определение. Существует множество мощных приемов программирования, вовлекающих такого рода вложенные функции-замыкания, и их использование довольно широко распространено в программировании на языке JavaScript. Замыкания могут выглядеть малопонятными при первом знакомстве, однако вам необходимо хорошо понимать их, чтобы чувствовать себя уверенно при их использовании. 14
14
Это старый термин, который отражает тот факт, что переменные функции привязаны к цепочке областей видимости, вследствие чего функция образует «замыкание» по своим переменным.
Первый шаг к пониманию замыканий - знакомство с правилами лексической области видимости, действующими для вложенных функций. Взгляните на следующий пример (который напоминает пример в разделе 3.10):
Понять суть замыканий будет совсем несложно, если усвоить правило лексической области видимости: во время выполнения функции используется цепочка областей видимости, которая действовала в момент ее определения. Однако некоторые программисты испытывают сложности при освоении замыканий, потому что не до конца понимают особенности реализации. Известно, думают они, что локальные переменные, объявленные во внешней функции, прекращают свое существование после выхода из внешней функции, но тогда как вложенная функция может использовать цепочку областей видимости, которая больше не существует? Если вы задавали себе такой вопрос, значит, у вас наверняка есть опыт работы с низкоуровневыми языками программирования, такими как С, и аппаратными архитектурами, использующими стек: если локальные переменные размещать на стеке, они действительно прекращают свое существование после завершения функции.
Но вспомните определение цепочки областей видимости из раздела 3.10.3. Там она описывалась как список объектов, а не стек. Каждый раз, когда интерпретатор JavaScript вызывает функцию, он создает новый объект для хранения локальных переменных этой функции, и этот объект добавляется в цепочку областей видимости. Когда функция возвращает управление, этот объект удаляется из цепочки. Если в программе нет вложенных функций и нет ссылок на этот объект, он будет утилизирован сборщиком мусора. Если в программе имеются вложенные функции, тогда каждая из этих функций будет владеть ссылкой на
свою цепочку областей видимости, а цепочка будет ссылаться на объекты с локальными переменными. Если объекты вложенных функций существуют только в пределах своих внешних функций, они сами будут утилизированы сборщиком мусора, а вместе с ними будут утилизированы и объекты с локальными переменными, на которые они ссылались. Но если функция определяет вложенную функцию и возвращает ее или сохраняет в свойстве какого-либо объекта, то образуется внешняя ссылка на вложенную функцию. Такой объект вложенной функции не будет утилизирован сборщиком мусора, и точно так же не будет утилизирован объект с локальными переменными, на который она ссылается.*********************************************************
Функция
В этой версии пара круглых скобок была перемещена из тела функции
Напомню главное правило лексической области видимости: при выполнении функции в языке JavaScript используется цепочка областей видимости, действовавшая на момент ее определения. Вложенная функция
В разделе 8.4.1 был приведен пример функции
Внимательно изучите этот пример, чтобы понять, как он действует. На первый взгляд, первая строка выглядит как инструкция присваивания функции переменной uniquelnteger. Фактически же это определение и вызов функции (как подсказывает открывающая круглая скобка в первой строке), поэтому в действительности переменной uniquelnteger присваивается значение, возвращаемое функцией. Если теперь обратить внимание на тело функции, можно увидеть, что она возвращает другую функцию. Именно этот объект вложенной функции и присваивается переменной uniquelnteger. Вложенная функция имеет доступ к переменным в ее области видимости и может использовать переменную counter, объявленную во внешней функции. После возврата из внешней функции никакой другой программный код не будет иметь доступа к переменной counter: вложенная функция будет обладать исключительным правом доступа к ней.