Привет, сегодня поговорим про хранение данных в замыкании, обещаю рассказать все что знаю. Для того чтобы лучше понимать что такое
хранение данных в замыкании, прием программирования модули на js , настоятельно рекомендую прочитать все из категории Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend).
Замыкания можно использовать сотнями способов. Иногда люди сами не замечают, что использовали замыкания — настолько это просто и естественно.
В этой главе мы рассмотрим примеры использования замыканий для хранения данных и задачи на эту тему.
Данные для счетчика
В примере ниже makeCounter
создает функцию, которая считает свои вызовы:
Хранение текущего числа вызовов осуществляется в переменной currentCount
внешней функции.
При этом, так как каждый вызов makeCounter
создает новый объект переменных, то все создаваемые функции-счетчики взаимно независимы.
Добавим счетчику аргумент, который, если передан, устанавливает значение:
Здесь для проверки первого аргумента использовано сравнение с undefined
, так что вызовыcounter(undefined)
и counter()
сработают идентично, как будто аргументов нет.
Объект счетчика
Можно пойти дальше и возвратить полноценный объект с функциями управления счетчиком:
getNext()
— получить следующее значение, то, что раньше делал вызов counter()
.
set(value)
— поставить значение.
reset()
— обнулить счетчик.
…Теперь counter
— объект с методами, которые при работе используют currentCount
. Снаружи никак иначе, кроме как через эти методы, к currentCount
получить доступ нельзя, так как это локальная переменная.
Объект счетчика + функция
К сожалению, пропал короткий красивый вызов counter()
, вместо него теперь counter.getNext()
. Но он ведь был таким коротким и удобным… Так что давайте вернем его:
Красиво, не правда ли? Тот факт, что объект — функция, вовсе не мешает добавить к нему сколько угодно методов.
Прием проектирования «Модуль»
Прием программирования «модуль» имеет громадное количество вариаций.
Его цель — объявить функции так, чтобы ненужные подробности их реализаций были скрыты. В том числе: временные переменные, константы, вспомогательные мини-функции и т.п.
Оформление кода в модуль предусматривает следующие шаги:
- Создается функция-обертка, которая выполняется «на месте»:
- Внутрь этой функции пишутся локальные переменные и функции, которые пользователю модуля не нужны, но нужны самому модулю:
Они будут доступны только изнутри.
- Те функции, которые нужны пользователю, «экспортируются» во внешнюю область видимости.
Если функция одна — это можно сделать явным возвратом return
:
…Если функций много — можно присвоить их напрямую в window
:
Или, что еще лучше, вернуть объект с ними:
-
Все функции модуля будут через замыкание иметь доступ к другим переменным и внутренним функциям. Но снаружи программист, использующий модуль, может обращаться напрямую только к тем, которые экспортированы.
Благодаря этому будут скрыты внутренние аспекты реализации, которые нужны только разработчику модуля.
Можно придумать и много других вариаций такого подхода. В конце концов, «модуль» — это всего лишь функция-обертка для скрытия переменных.
Задачи на понимание замыканий
Важность: 4
Чтобы вторые скобки в вызове работали - первые должны возвращать функцию.
Эта функция должна знать про a
и уметь прибавлять a
к b
. Об этом говорит сайт https://intellect.icu . Вот так:
Важность: 5
Текущее значение текста удобно хранить в замыкании, в локальной переменнойmakeBuffer
:
Начальное значение text = ''
— пустая строка. Поэтому операция text += piece
прибавляет piece
к строке, автоматически преобразуя его к строковому типу, как и требовалось в условии.
Важность: 5
- Да, будет работать, благодаря ссылке
[[Scope]]
на внешний объект переменных, которая будет присвоена функциям sayHi
и yell
при создании объекта.
- Нет,
name
не удалится из памяти, поскольку несмотря на то, что sayHi
больше нет, есть еще функция yell
, которая также ссылается на внешний объект переменных. Этот объект хранится целиком, вместе со всеми свойствами.
При этом, так как функция sayHi
удалена из объекта и ссылок на нее нет, то больше к переменной name
обращаться некому. Получилось, что она «застряла» в памяти, хотя, по сути, никому не нужна.
- Если и
sayHi
и yell
удалить, тогда, так как больше внутренних функций не останется, удалится и объект переменных вместе с name
.
Важность: 5
Функция makeArmy
делает следующее:
- Создает пустой массив
shooter
:
- В цикле заполняет массив элементами через
shooter.push
.
При этом каждый элемент массива — это функция, так что в итоге после цикла массив будет таким:
Этот массив возвращается из функции.
- Вызов
army ()
— это получение элемента массива (им будет функция) на позиции 5
, и тут же — ее запуск.
Вначале разберемся, почему все стрелки выводят одно и то же значение.
В функциях-стрелках shooter
отсутствует переменная i
. Когда такая функция вызывается, то i
она берет из внешнего LexicalEnvironment
…
Каким будет значение i
?
К моменту вызова army ()
, функция makeArmy
уже закончила работу. Цикл завершился, последнее значение было i=10
.
В результате все функции shooter
получают одно и то же, последнее, значение i=10
.
Попробуйте исправить проблему самостоятельно.
Есть несколько способов исправить ситуацию.
- Первый способ исправить код - это привязать значение непосредственно к функции-стрелку:
В этом случае каждая функция хранит в себе свой собственный номер.
Кстати, обратите внимание на использование Named Function Expression, вот в этом участке:
Если убрать имя me
и оставить обращение через shooter
, то работать не будет:
Вызов alert(shooter.i)
при вызове будет искать переменную shooter
, а эта переменная меняет значение по ходу цикла, и к моменту вызову она равна последней функции, созданной в цикле.
Если использовать Named Function Expression, то имя жестко привязывается к конкретной функции, и поэтому в коде выше me.i
возвращает правильный i
.
- Другое, более продвинутое решение —- использовать дополнительную функцию для того, чтобы «поймать» текущее значение
i
:
Посмотрим выделенный фрагмент более внимательно, чтобы понять, что происходит:
Функция shooter
создана как результат вызова промежуточного функционального выражения function(x)
, которое объявляется — и тут же выполняется, получая x = i
.
Так как function(x)
тут же завершается, то значение x
больше не меняется. Оно и будет использовано в возвращаемой функции-стрелке.
Для красоты можно изменить название переменной x
на i
, суть происходящего при этом не изменится:
Кстати, обратите внимание — скобки вокруг function(i)
не нужны, можно и так:
Скобки добавлены в код для лучшей читаемости, чтобы человек, который просматривает его, не подумал, что var shooter = function
, а понял что это вызов «на месте», и присваивается его результат.
- Еще один забавный способ - обернуть весь цикл во временную функцию:
Вызов (function(i) { ... })
обернут в скобки, чтобы интерпретатор понял, что это Function Expression
.
Плюс этого способа - в большей читаемости. Фактически, мы не меняем создание shooter
, а просто обертываем итерацию в функцию.
К сожалению, в одной статье не просто дать все знания про хранение данных в замыкании. Но я - старался.
Если ты проявишь интерес к раскрытию подробностей,я обязательно напишу продолжение! Надеюсь, что теперь ты понял что такое хранение данных в замыкании, прием программирования модули на js
и для чего все это нужно, а если не понял, или есть замечания,
то не стесняйся, пиши или спрашивай в комментариях, с удовольствием отвечу. Для того чтобы глубже понять настоятельно рекомендую изучить всю информацию из категории
Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend)
Комментарии
Оставить комментарий
Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend)
Термины: Выполнение скриптов на стороне клиента JavaScript, jqvery, JS фреймворки (Frontend)