this, call, apply, bind
JavaScript простым языком
Last updated
Was this helpful?
JavaScript простым языком
Last updated
Was this helpful?
В этом уроке расскажу о таких надуманно сложных вещах, как call
, bind
, apply
, ну и, соответственно, затрону this
. Все эти слова связаны одним словом – контекст. Но, обо всем по порядку.
Многие боятся даже подходить к этой теме, потому как им кажется, что это что-то невероятно сложная и непонятная тема для новичков. Поэтому, надеюсь, что сегодня я развенчаю все ваши страхи и непонятки по этой теме.
Все примеры будут сделаны на основе функций, поэтому нужно представлять как они работают.
Создадим обычную функцию и сразу же вызовем её:
Функция выводит в консоль слово Привет
и this
. Если заглянуть в консоль, то увидим следующее:
Если со словом Привет
все понятно, то вот что это такое вывелось на месте this
? Почему вывелся какой-то объект Window
?
Когда мы создаем глобальные объекты, то они автоматически начинают принадлежать глобальному объекту window
– самому главному объекту в браузере.
На самом деле все методы, которые ты часто вызываешь, например: console.log()
, alert
, prompt
и т.д. находятся внутри объекта window
и являются его методами.
Это достаточно просто проверить. Просто попробуй вызвать эти методы следующим образом:
window.console.log('Привет');
window.alert('Привет');
window.prompt('Как тебя зовут?');
Надеюсь ты протестировал и понял, что абсолютно ничего не изменилось. А теперь, вернемся к нашей функции:
На самом деле, когда мы вызываем функцию, то мы вызываем её так же из объекта window
, т.е. вызов нашей функции можно переписать так:
Из всего выше сказанного можно сделать вывод, что наша функция запускается из объекта window
, т.е. контекст, в котором выполняется наша функция так же равна объекту window
. А ключевое слово this
как раз и содержит внутри себя контекст, в котором вызывается функция/метод.
А теперь создадим объект:
Простой объект описывающий абстрактного мужчину. Обрати внимание на метод sayHi
. В него мы передали ссылку на функцию, которую создали в самом начале.
Давай вызовем метод sayHi
из нашего объекта:
А теперь посмотрим в консоль, что же теперь вывелось на месте this
. Получаем следующий результат:
В этом случае значение this
(контекста) становится равным самому объекту. Почему так случилось? Все очень просто.
Первый раз мы вызывали функцию hi
и значение this
, внутри неё было равно объекту Window
, потому что мы запускали функцию в контексте объекта Window
.
А сейчас, мы создали свой объект, и запустили функцию hi
из него. Получается, что место вызова функции изменилось с объекта Window
на объект man
. Следовательно, изменилось и значение this с Window
на man
.
Теперь коротко: значение this
равно тому объекту в контексте которого было вызвано.
С контекстом разобрались, давай разбираться с этими страшными методами: call
, apply
, bind
.
Мы ранее создали объект man
, а теперь еще создадим объект woman
. Итого, получим:
Итак, мы имеем 2 объекта, один из которых описывает мужчину, а второй женщину.
Для примера, создадим еще один объект (логгер), которому добавим один метод:
Внутри объекта Logger
мы создали метод info
, который должен выводить информацию о наших объектах. Мы использовали ключевое слово this
внутри этого метода:
Но что будет, если вызвать этот метод прямо сейчас?
Результат:
Мы получили undefined
во всех случаях и это более чем логично. Т.к. мы обращаемся к ключевому слову this
внутри метода info
, который принадлежит объекту Logger
, то и контекст, т.е. само ключевое this
имеет значение, которое равно объекту Logger
. А внутри этого объекта у нас имеется только один метод info
. Никаких свойств с именами name
, lastName
, age
у нас нет.
Как сделать так, чтобы с помощью объекта Logger
и его метода info
вывести данные, например, об объекте man
?
Как раз здесь на помощь приходит тот самый страшный метод bind
:
У каждой функции в JavaScript существует метод bind
, благодаря которому мы можем любой функции задать контекст внутри которого она должна будет выполняться.
Метод bind
возвращает новую функцию к которой будет привязан тот контекст, который мы указали как аргумент метода bind
. В нашем случае контекстом мы указали объект man
. Так как bind
возвращает новую функцию, то мы её запишем в константу loggerMan
.
Теперь вызовем полученную функцию:
Результат:
Как видишь, bind
успешно привязал в качестве контекста объект man
и информация о нём успешно вывелась в консоль.
Теперь мы хотим получить информацию об объекте woman
с помощью нашего логгера
. Для этого делаем то же самое:
С помощью bind
мы привязываем новый контекст для метода info
со значениемwoman
.
Вызовем:
Результат:
Вуаля, все так же прекрасно работает. И ведь совсем не сложно и не страшно, верно?
И последнее, что хотелось бы здесь отметить. В функцию info
мы можем дополнительно передавать какие-либо параметры, если это нужно. Давай немного исправим метод info
в объекте Logger
. Пускай он будет принимать пол человека:
Все, что мы сделали -- добавили в определение функции параметр sex
и вывели его под остальными данными. Остается вопрос, как нам туда это все передать, если нам еще и контекст нужно сменить?! Все очень просто. У нас уже есть полученные функции для наших объектов:
Чтобы передать параметр sex
, нам достаточно указать его аргументом наших функций при их вызове:
Смотрим на результат:
Все прекрасно отработало. Но это не единственный способ передачи параметров.
Есть еще один способ передачи параметров – указать их при вызове метода bind
.
Получим следующее:
Результат:
Результат никак не поменялся и все прекрасно продолжает работать.
Первый аргумент в методе bind
– это сам контекст, который нужно привязать к функции. А вот дальше, через запятую, можно передавать сколько угодно дополнительных параметров (аргументов), которые должны попасть в метод info
. В нашем случае нам нужно было передать только один аргумент sex
.
На этом с bind
всё. Не такой уж и страшный зверь, если приглядеться.
Что ж, продолжим убивать страх внутри тебя. Теперь на очереди метод call
.
Ты не поверишь, но он делает то же самое, что и bind
. С одной маленькой поправкой: если метод bind
привязывает контекст и возвращает новую функцию с этим контекстом, то метод call
привязывая контекст, сразу же вызывает указанную функцию, а не возвращает новую.
Продублирую наш код:
Итак, давай привяжем контекст с помощью call
, а не bind
и посмотрим в чем же разница.
Было:
Стало:
Так как метод call
после привязки контекста сразу же выполняет функцию (info
), и наша функция ничего не возвращает (внутри нет ключевого слова return
), то не имеет никакого смысла записывать результат данной функции, так как он всегда в нашем случае будет равен undefined
, поэтому создание констант loggerMan
и loggerWoman
делать не нужно.
Вот так вот просто. call
– ничуть не страшнее bind
. Вся разница лишь в том, что:
bind
привязывает контекст и возвращает новую функцию и мы можем вызвать её в любом месте;
call
привязывает контекст и сразу же вызывает функцию
Собственно, больше о call
и сказать нечего.
Ты должно быть заметил, что о методе bind
было сказано очень много. О методе call
– уже куда меньше, потому что это одно и тоже с одним исключением. Будешь смеяться, но метод apply
– это то же самое, что и call
. И существует только одно отличие.
Если метод call
(как, собственно, и bind
) первым аргументом принимает контекст, а дальше через запятую принимает аргументы для функции (получается, что аргументов может быть разное количество), то метод apply
принимает всего 2 аргумента:
Контекст (так же как bind
и call
);
Массив параметров.
Т.е. если в bind
и call
мы можем передавать параметры функции через запятую, то в apply
мы должны передать их в массиве. И в этом вся разница. Давай перепишем код с call
на apply
.
Было:
Стало:
Вот собственно и вся разница. В случае с call
мы передали аргумент sex
для функции info
как обычную строку. А в случае с apply
мы обязаны передавать все аргументы в массиве.
Надеюсь, теперь ты поборол свой страх и понял, что всё, что связано с контекстами, конкретно, данными методами – совсем не страшно.
Предыдущие уроки по функциям: ,