Функции в JavaScript - это наиболее используемые конструкции кода. Их можно вызывать столько раз, сколько вам необходимо, назначать в качестве обработчиков события для разных элементов, использовать в виде методов класса. Однако есть ряд особенностей использования функций, которые стоит знать, т.к. иначе можно натолкнуться на необъяснимое поведение некоторых переменных и самих функций.

Подъем функций (hoisting)

В JavaScipt есть понятие hoisting - подъема определений функций и переменных, объявленных с помощью ключевого слова var. Заключается этот процесс в том,что интерпретатор JavaScript, получая код, просматривает его на предмет наличия в нем ключевых слов var и function(), а затем как бы отправляет их в самое начало кода. В этом случае любая функция доступна для вызова до того, как она объявлена:

Несмотря на то, что функция info() описана в строке 4, вызов функции выполняется без проблем в строках 1 и 2.

Поднятие также существует для переменных: все переменные, объявленные с помощью ключевого слова var, поднимаются в начало кода и инициализируются сначала значением undefined.

Приоритет поднятия переменных и функций

Есть ряд моментов при объявлении и поднятии функций и переменных в JavaScript:

  1. Инициализация переменных имеет приоритет перед объявлением функции.
  2. Объявление функции имеет приоритет перед объявлением переменной.
  3. Объявления функций "поднимаются" над объявлением переменных, но не над их инициализацией.

Это значит, что переменная, объявленная с тем же именем, что и функция, не всегда будет перезаписана функцией. В примере ниже мы видим, что переменная sq не только объявляется, но и инициализируется значением 12. После объявления переменной была объявлена функция с тем же именем sq. В консоли мы видим, что значение sq - это число 12.

Второй вариант - когда мы объявляем переменную, но не задаем ей значение:

Теперь в консоли мы видим код функции и тип function, а не значение переменной. Это следует учитывать, т.к. при написании кода вы можете случайно использовать одинаковые имена для переменных и функций, хотя этого делать, конечно, не стоит.

Немедленно вызываемые функции (IIFE)

Обычно немедленно вызываемые функции (IIFE - Immediately Invoked Function Expression)  используют для того, чтобы локализовать внутри функции область видимости переменных. Очень часто такой подход используют для оборачивания кода во всем скрипте, чтобы объявленные переменные не переопределяли другие переменные из сторонних библиотек.

Для того чтобы функция превратилась в немедленно вызываемую, необходимо обернуть ее в скобки и вызвать, используя ():

Пример такой функции вы увидите сразу же при открытии этой страницы:

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

Результат работы функций представлен ниже. Попробуйте изменить параметр в скобках вызова второй функции на отрицательное число - и фон абзаца поменяется.

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

Особенности передачи параметров в javascript-функции

В Javascript переменные, которые передаются в функцию, могут иметь сложный тип (Object, Array) или простой (String, Number, Boolean). Когда в качестве аргумента передаётся сложный тип (объект), передача происходит по ссылке. Вместо отправки копии переменной, Javascript указывает ссылку на место в оперативной памяти компьютера, где расположен данный объект. Это и называется передачей аргумента по ссылке. Если используется переменная простого типа (строка или число, или true или false, например), то передача происходит по значению. Этот нюанс может привести к скрытым ошибкам и непониманию того, что происходит. Ниже рассмотрим передачу по ссылке и по значению.

В функцию changeNum() мы передаем значение переменной num, которая равна 48. Это видно в console.log()  в строке. Затем функция добавляет к этому значению 12 и выводит 60 в строке. Однако при выводе в console.log() значения переменной num оказывается, что она снова равна изначальному значению, т.е. 48.

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

Есть вторая сторона этого примера: когда мы не передаем параметр в функцию, а просто используем внешнюю переменную в этой функции. Тогда значение этой переменной будет изменено:

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

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

В начале кода объявлен объект со свойством name: "John Doe". Затем в функцию changeObjName(obj) передается этот объект, а в функции меняется только имя объекта. И, действительно, при выводе в консоль данных об объекте в строке 9 выводится новое имя name: "Mila Born". Вторая функция changeObj(object) получает тот же объект и возвращает новый с измененным именем. Если вывести в консоль результат работы функции, то мы увидим  {name: "Vasya Pupkin"}, однако при выводе самого объекта obj в свойстве name будет предыдущее значение "Mila Born". Это говорит о том, что передаваемый в функцию объект и возвращаемый из нее - это разные объекты.

Callback-функции, или функции обратного вызова

В JS функции могут принимать другие функции в качестве аргументов и возвращать функции. Callback-функция, или функция обратного вызова - это такая функция, которая передается внутрь другой функции, как аргумент. Такие функции сплошь и рядом используются в JavaScript для обработки событий, например, внутри метода addEventListener():

Проверьте сами:

Сделай текст синим - кликни по абзацу

Второй очень распространенный пример - это колбеки внутри методов массивов, таких, как forEach(), reduce(), map(), filter():

Сам пример:

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

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

В примере ниже мы с помощью функции showResult() запускаем передаваемую ей функцию f(), в которой вызваны 2 других функции, определяющие  произведение и сумму 2-х чисел:

В результате в консоли мы увидим 2 строки:

функция-callback

Еще один пример callback-функции вы найдете в статье "Создание и использование таблиц CSS-стилей в JS", где после загрузки таблицы стилей выдается диалоговое окно alert() с информацией, что стилевая таблица загружена. В код на самом деле можно передать любую функцию, которая сработает именно после загрузки таблицы стилей.

Замыкание

Замыкание — это функция, использующая независимые переменные. Иными словами, это функция в функции, которая  знает и помнит значения переменных в окружении, в котором она была создана.

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

Классический пример замыкания - это создание счетчиков. В функции counter задается начальное значение переменной count=1.  Это локальная переменная, находящаяся в области видимости функции counter . Она не видна извне, из глобальной области видимости, зато к ней может получить доступ вложенная анонимная функция, которая возвращается в из основной (внешней) функции counter().  При создании переменной myNum мы фактически получаем код функции, который увеличивает невидимую извне переменную count на 1 (8-я строка кода). Именно возможность работать со ссылкой на экземпляр локальной переменной называется замыканием. Функция, замыкающая локальные переменные, называется замыкающей.

Поскольку myNum - это функция, а не числовая или строковая переменная, мы можем вызвать ее, используя круглые скобки в конце, как и любую другую функцию. Однако в момент такого вызова мы увеличиваем значение переменной count на 1 и возвращаем его в место вызова. Само значение переменной count было "запомнено" при создании переменной myNum, поэтому оно последовательно увеличивается с каждым новым вызовом myNum().

Самое интересное заключается в том, что при создании другой переменной (counter2 в 12-й строке в примере выше) мы получаем другой, отдельный счетчик, который стартует с 1 и увеличивается при вызове  counter2().

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

Пример использования замыкания внутри функции-конструктора объекта

Например, у нас есть функция-конструктор Animal, которая при создании экземпляра cat требует указания имени, возраста и длины животного. Внутри функции у нас есть переменная toy, которой присвоено значение 'Моток веревки', и вторая переменная prirost со случайным значением от 2 до 12.  Также в функции-конструкторе Animal описан метод grow(), который при вызове увеличивает длину животного на величину, рассчитанную в переменной prirost. Эта переменная как раз и есть та, которую "запоминает" экземпляр Animal с именем cat, а затем использует при повторном вызове метода grow(). В приведенном примере прирост составил 6 см, и именно на столько каждый раз при вызове метода grow() увеличивалась длина кота. То есть метод grow() - это как раз вариант использования замыкания.

Если мы попытаемся вывести в консоль значение переменных toy и prirost, то получим в строках 16 и 21 значение undefined, т.к. извне доступа к ним нет. Зато можно создать метод getToy(), который получает эначение переменной toy, и метод setToy(), который устанавливает новое значение.

Если же задать свойство toy явно строкой cat.toy = 'заводная мышка', то все равно мы не получим доступа к внутренней переменной toy объекта Animal. Это будет просто еще одно дополнительное свойство объекта cat. Методы  getToy() и setToy() никак не будут им управлять. Они замыкаются на свойсте toy внутри Animal и управляют только им. По сути, toy и prirost - это приватные переменные объекта Animal, т.к. добраться до них извне без использования методов нельзя.

Пример использованиея замыкания для создания нескольких абзацев подобных цветов

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

В функции changeColor() объявим 3 переменные, которые будут формировать 3 составляющие цвета в системе rgb(). Затем во вложенной функции darker() мы отнимаем от полученных значений 20 единиц, проверяя значение каждой из 3 переменных на то, чтобы оно было больше 20.

Затем создаем переменную color, связанную с внешней функцией и обращаемся к вложенной функции при формировании абзацев методом document.write() в стилях. В комментариях рядом со строками формирующими абзацы, записан один из вариантов сгенерированных цветов. Там видно, что каждая из единиц цвета уменьшается на 20.

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

Автор: Админ

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *