Если вы читаете эту статью, то наверняка уже пробовали разобраться с тем, для чего в JavaScript существуют методы bind(), call() и apply().  Проблема понимания этих методов заключается в том, что их часто преподносят таким образом, что они кажутся более сложными, чем они хотелось бы, и вы снова откладываете возможность разобраться с bind(), call() и apply() до лучших времен.

Что же такое особенное есть в  bind (), call () и apply()? Как они работают?

Чтобы ответить на этот вопрос, сначала важно вспомнить, что в JavaScript все функции являются объектами. Это означает, что они могут иметь свойства и методы, как и любой другой объект. Функции - это особый тип объектов, поскольку они имеют множество встроенных свойств и методов, три из которых - call(), apply() и bind().

Для того чтобы разобраться со всеми этими методами, мы создадим объект person с двумя свойствами и методом. Метод будет задан функцией showFullName(), использующей ключевое слово this.

Кроме того, нам понадобится функция getSkills(), которая у нас будет существовать отдельно от объекта person:

Эта функция выводит некоторую конформацию о навыках нашего объекта, используя его собственную функцию showFullName() и передаваемые в нее параметры. Мы будем использовать методы bind(), call() и apply() для того, чтобы иметь возможность вызвать функцию getSkills() для нашего объекта person.

Если мы вызовем getSkills() прямо сейчас, мы получим ошибку (4 и 5-строки кода). Это связано с тем, что функция getSkills() определена в глобальной области видимости, поэтому this указывает на глобальный объект window, у которого нет метода getFullName() , а у объекта person отсутствует метод getSkills().

Глобальная функция

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

Метод bind()

Из документации на MDN узнаем, что

Метод bind() создаёт новую функцию, которая при вызове устанавливает в качестве контекста выполнения this предоставленное значение. В метод также передаётся набор аргументов, которые будут установлены перед переданными в привязанную функцию аргументами при её вызове.

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

Вспомогательной функцию назовем  personSkills, и запишем в нее вызов глобальной функции getSkills, передав через метод bind() наш объект person. Теперь ключевое слово this указывает на объект person, который мы передали в качестве аргумента в bind(). Код теперь выглядит так:

В результате таких действий в консоли браузера будет выведено:

Обратите внимание на то, что метод bind() всегда возвращает новую функцию. Он создает копию getSkills() и сообщает механизму JavaScript: «Каждый раз, когда вызывается эта копия getSkills(), установите ключевое слово this для ссылки на person во время ее выполнения». Это отличает bind()  от методов call() и apply(), которые ждут нас впереди.

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

Метод call()

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

Метод call() вызывает функцию с указанным значением this и индивидуально предоставленными аргументами.

Несколько переписываем наш код:

Очень просто по сравнению с bind(), не так ли?

Вместо аргументов "HTML/CSS" и "JavaScript" при вызове глобальной функции getSkills() были использованы 'PHP' и 'Python', чтобы посмотреть на работоспособность вызова функции с call() - и все сработало! В отличие от bind(), call() не копирует функцию. Он позволяет передавать объект в качестве this и любые аргументы, а затем немедленно вызывает функцию.

Метод apply()

Методы apply() и call() практически идентичны при работе с выставлением значения this, за исключением того, что вы передаёте параметры функции в apply() как массив, в то время, как в call(), параметры передаются в индивидуальном порядке. Давайте опять обратимся к документации на MDN:

Метод apply() вызывает функцию с указанным значением this и аргументами, предоставленными в виде массива (либо массивоподобного объекта).

Это значит, что при вызове apply() для нашей функции getSkills() параметры нужно передать в квадратных скобках, как массив - и все. Результат:

Вот, собственно, и все, что касается применения методов bind(), call() и apply() для связывания this объекта и внешней функции. Однако практическое использование этих методов не исчерпывается только лишь этой ситуацией. Например, эти методы с успехом можно применять для различных действий с массивами или псевдомассивами (аргументами функций или коллекциями HTML-элементов).

Практическое применение методов call(), apply(), bind()

Метод call()

Метод call() периодически используется (или, скорей, использовался ранее) для преобразование массивоподобных объектов в массивы. Так, например, свойство arguments, которое существует внутри каждой функции - это не совсем массив, хотя каждый элемент в нем имеет свой числовой индекс. Это можно проверить строкой ниже, которая  выведет в консоль false:

Для того чтобы преобразовать arguments в массив, используем call() при вызове метода массива Array.prototype.forEach. Будем рассматривать функцию подсчета суммы различного количества чисел:

Раньше для преобразования коллекций в массивы приходилось использовать подобные конструкции:

Сейчас можно воспользоваться методом Array.from(псевдомассив) для получения такого же результата:

Пример использования метода call()  совместно с  методом массивов slice() для преобразования коллекции html-элементов (псевдомасива) в обычный  массив:

See the Pen Array.slice.call() for HTMLCollection by Elen (@ambassador) on CodePen.

Практический пример использования метода call() c codepen.io от автора Kyle Edwards при работе с массивами объектов.

See the Pen Meaningful Transitions by Kyle Edwards (@kyledws) on CodePen.

Метод apply()

Вы можете вывести элементы массива в консоль с помощью метода apply() либо воспользоваться для этого появившимся в стандарте ES6 оператором spread ... :

Для определения минимального и максимального элемента в массиве есть вариант использования метода apply() для Math.min() и Math.max() от создателя jQuery Джона Ресига:

Альтернатива применению  методу apply() - использование spread-оператора для передачи массива в методы объекта Math.

Метод bind()

В примере ниже метод bind() поможет  нам создать выборку, подобную jQuery селекторам, а затем применить метод  addEventListener для создания обработчиков событий, подобных jQuery-методу on():

Посмотрим, как работает пример в действии. Наведите на любой из абзацев - и увидите изменение его фона. Клик на среднем абзаце будет увеличивать его размер, отталкиваясь либо от текущего значения font-size в атрибуте style этого элемента, или от того, который берется из таблицы стилей с помощью метода window.getComputedStyle().

Lorem ipsum dolor sit amet, consectetur adipisicing elit. Debitis, inventore reprehenderit alias, rem deserunt repudiandae id nihil vitae corporis! Odit veniam unde, tempora voluptatem iure adipisci impedit velit quidem labore?

Porro nisi provident nostrum aspernatur, voluptates ex consequatur quaerat modi, quis iste, sapiente atque error vero temporibus velit maxime dolore. Accusantium quibusdam est enim doloribus. Quod sint quaerat quisquam nemo!

Blanditiis doloremque sed reprehenderit, magni neque aut a itaque aperiam consequatur consectetur inventore, earum, dolorum aspernatur odio repellat incidunt aliquid non. Ab aperiam accusantium ducimus ratione, ea odit delectus repudiandae.

Аналогичным способом можно добавить обработку событий на тач-экранах:

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

При каррировании функции создается копия функции с некоторыми предустановленными параметрами. Чем может быть полезен для этого bind(), если он тоже занимается созданием копии функции?

Пример использования объекта для открытия/закрытия модального окна с помощью метода bind()

В этом примере метод bind() понадобится в 2-х случаях:

  1. При вызове определенного метода объекта во время срабатывания события клика, когда this переходит к тому объекту, на котором был сделан клик
  2. При вызове определенных методов объекта с задержкой с помощью метода setTimeout().

HTML-разметка примера:

Открываем модальное окно по клику на ссылке с id="addPopup".

CSS-стили примера:

Изначально модальное окно .my-popup скрыто с помощью свойства display: none и будет открыто при добавлении к нему класса .open.

JavaScript-код примера:

Тестируем пример:

В коде мы используем метод bind(), чтобы связать this с объектом popupViewer, который имеет те методы, которые мы вызываем при клике. Однако, при использовании метода  addEventListener ключевое слово this указывает либо на ссылку, либо на вложенный объект. Поэтому с помощью метода bind() мы возвращаем this  к объекту  popupViewer (строки 26, 27).

Когда в коде используются методы setTimeout() или setInterval(), которые принадлежат объекту window, this также перестает указывать на объект, внутри которого они вызываются, и относится к  window. Поэтому, необходимо "забайндить"  this, указав для setTimeout() в строке 10, что мы будем использовать внутри функции ссылку на текущий объект. Вторым способом "вернуть" ссылку на this является использование стрелочной функции (строка 13).

Примеры использования метода bind

Счетчик от Jon Kantner

See the Pen Bouncy Counter by Jon Kantner (@jkantner) on CodePen.

И еще один пример использования функции bind() - игра Память (Memory)  на основе класса MemoryGame.

See the Pen Memory Game by Jonathan Tarnate (@jeytii) on CodePen.

Ссылки по теме:

Автор: Админ

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

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