Методы объекта, "this"
Объекты обычно создаются, чтобы представлять сущности реального мира, будь то пользователи, заказы и так далее:
И так же, как и в реальном мире, пользователь может совершать действия: выбирать что-то из корзины покупок, авторизовываться, выходить из системы, оплачивать и т.п.
Такие действия в JavaScript представлены функциями в свойствах.
Примеры методов
Для начала давайте научим нашего пользователя user здороваться:
Здесь мы просто использовали Function Expression (функциональное выражение), чтобы создать функцию приветствия, и присвоили её свойству user.sayHi нашего объекта.
Затем мы можем вызвать ee как user.sayHi() . Теперь пользователь может говорить!
Функцию, которая является свойством объекта, называют методом этого объекта.
Итак, мы получили метод sayHi объекта user .
Конечно, мы могли бы использовать заранее объявленную функцию в качестве метода, вот так:
Когда мы пишем наш код, используя объекты для представления сущностей реального мира, – это называется объектно-ориентированным программированием или сокращённо: «ООП».
ООП является большой предметной областью и интересной наукой самой по себе. Как выбрать правильные сущности? Как организовать взаимодействие между ними? Это – создание архитектуры, и на эту тему есть отличные книги, такие как «Приёмы объектно-ориентированного проектирования. Паттерны проектирования» авторов Эрих Гамма, Ричард Хелм, Ральф Джонсон, Джон Влиссидес или «Объектно-ориентированный анализ и проектирование с примерами приложений» Гради Буча, а также ещё множество других книг.
Сокращённая запись метода
Существует более короткий синтаксис для методов в литерале объекта:
Как было показано, мы можем пропустить ключевое слово "function" и просто написать sayHi() .
Нужно отметить, что эти две записи не полностью эквивалентны. Есть тонкие различия, связанные с наследованием объектов (что будет рассмотрено позже), но на данном этапе изучения это неважно. Почти во всех случаях сокращённый синтаксис предпочтителен.
Ключевое слово «this» в методах
Как правило, методу объекта обычно требуется доступ к информации, хранящейся в объекте, для выполнения своей работы.
Например, коду внутри user.sayHi() может потребоваться имя пользователя, которое хранится в объекте user .
Для доступа к информации внутри объекта метод может использовать ключевое слово this .
Значение this – это объект «перед точкой», который используется для вызова метода.
Здесь во время выполнения кода user.sayHi() значением this будет являться user (ссылка на объект user ).
Технически также возможно получить доступ к объекту без ключевого слова this , обратившись к нему через внешнюю переменную (в которой хранится ссылка на этот объект):
…Но такой код ненадёжен. Если мы решим скопировать ссылку на объект user в другую переменную, например, admin = user , и перезапишем переменную user чем-то другим, тогда будет осуществлён доступ к неправильному объекту при вызове метода из admin .
Это показано ниже:
Если бы мы использовали this.name вместо user.name внутри alert , тогда этот код бы сработал.
«this» не является фиксированным
В JavaScript ключевое слово «this» ведёт себя иначе, чем в большинстве других языков программирования. Его можно использовать в любой функции, даже если это не метод объекта.
В следующем примере нет синтаксической ошибки:
Значение this вычисляется во время выполнения кода, в зависимости от контекста.
Например, здесь одна и та же функция назначена двум разным объектам и имеет различное значение «this» в вызовах:
Правило простое: если вызывается obj.f() , то во время вызова f , this – это obj . Так что, в приведённом выше примере это либо user , либо admin .
Мы даже можем вызвать функцию вообще без объекта:
В строгом режиме ( "use strict" ) в таком коде значением this будет являться undefined . Если мы попытаемся получить доступ к this.name – это вызовет ошибку.
В нестрогом режиме значением this в таком случае будет глобальный объект ( window в браузерe, мы вернёмся к этому позже в главе Глобальный объект). Это – исторически сложившееся поведение this , которое исправляется использованием строгого режима ( "use strict" ).
Обычно подобный вызов является ошибкой программирования. Если внутри функции используется this , тогда она ожидает, что будет вызвана в контексте какого-либо объекта.
Если вы до этого изучали другие языки программирования, то вы, вероятно, привыкли к идее «фиксированного this » – когда методы, определённые в объекте, всегда имеют this , ссылающееся на этот объект.
В JavaScript this является «свободным», его значение вычисляется в момент вызова метода и не зависит от того, где этот метод был объявлен, а скорее от того, какой объект вызывает метод (какой объект стоит «перед точкой»).
Эта концепция вычисления this в момент исполнения имеет как свои плюсы, так и минусы. С одной стороны, функция может быть повторно использована в качестве метода у различных объектов (что повышает гибкость). С другой стороны, большая гибкость увеличивает вероятность ошибок.
Здесь наша позиция заключается не в том, чтобы судить, является ли это архитектурное решение в языке хорошим или плохим. Скоро мы поймем, как с этим работать, как получить выгоду и избежать проблем.
У стрелочных функций нет «this»
Стрелочные функции особенные: у них нет своего «собственного» this . Если мы ссылаемся на this внутри такой функции, то оно берётся из внешней «нормальной» функции.
Например, здесь arrow() использует значение this из внешнего метода user.sayHi() :
Это особенность стрелочных функций. Она полезна, когда мы на самом деле не хотим иметь отдельное this , а скорее хотим взять его из внешнего контекста. Позже в главе Повторяем стрелочные функции мы увидим больше примеров на эту тему.
Итого
- Функции, которые находятся в свойствах объекта, называются «методами».
- Методы позволяют объектам «действовать»: object.doSomething() .
- Методы могут ссылаться на объект через this .
Значение this определяется во время исполнения кода.
- При объявлении любой функции в ней можно использовать this , но этот this не имеет значения до тех пор, пока функция не будет вызвана.
- Функция может быть скопирована между объектами (из одного объекта в другой).
- Когда функция вызывается синтаксисом «метода» – object.method() , значением this во время вызова является object .
Также ещё раз заметим, что стрелочные функции являются особенными – у них нет this . Когда внутри стрелочной функции обращаются к this , то его значение берётся извне.
Задачи
Использование "this" в литерале объекта
Здесь функция makeUser возвращает объект.
Каким будет результат при обращении к свойству объекта ref ? Почему?
Ответ: ошибка.
Это потому, что правила, которые определяют значение this , никак не смотрят на объявление объекта. Важен лишь момент вызова.
Здесь значение this внутри makeUser() равно undefined , потому что оно вызывается как функция, а не через «точечный» синтаксис как метод.
Значение this одно для всей функции, блоки кода и объектные литералы на него не влияют.
Таким образом, ref: this фактически принимает текущее this функции makeUser() .
Мы можем переписать функцию и вернуть то же самое this со значением undefined :
Ключевое слово this и контекст в JavaScript
![]()
К освоению JavaScript лежит долгий путь, на котором вам может встретиться такое выражение, как this . Первый раз я встретил его в процессе работы с eventListeners и jQuery, а в дальнейшем часто применял с React.
Овладение концепцией использования this и умение применять его со знанием дела не представляют особых трудностей.
Основы this
Попытки разобраться в том, что такое this , могут вызвать множество затруднений, даже из-за самого названия ключевого слова.
this тесно связано с тем, в каком контексте вы находитесь в программе. Начнем с самого начала. При наборе this консоль выдает объект window , самый внешний контекст JavaScript. При вводе следующего кода в Node.js:
мы получаем пустой объект <> . Это немного странно, но, скорее всего, это особенность Node.js. Однако при вводе следующего:
получаем объект global , относящийся к самому внешнему контексту. В этом контексте сохранены setTimeout и setInterval . Не бойтесь немного поэкспериментировать с ними, чтобы узнать все их возможности. С этого момента между Node.js и браузером почти нет никаких различий. Мы будем использовать объект window . Не забывайте, что в Node.js это будет объект global , хотя это не играет особой роли.
Запомните: Контекст имеет смысл только внутри функций.
Представьте, что вы пишите программу с функциями без вложений. Вы просто пишите одну строку за другой, не создавая никаких структур. Это значит, что нет необходимости следить за тем, где вы находитесь, поскольку вы остаетесь на одном уровне.
При наличии функций, программа обладает несколькими уровнями, а ключевое слово this указывает на то, где вы находитесь, и какой именно объект является функцией.
Отслеживание объекта caller
Рассмотрим изменения ключевого слова this в зависимости от контекста:
Поскольку был совершен вызов функции, объявленной внутри объекта coffee , контекст меняется именно на этот объект. Теперь можно получить доступ ко всем свойствам этого объекта с помощью this . В приведенном выше примере можно получить прямую ссылку на объект с помощью coffee.strong . Становится интереснее, когда мы не знаем, в каком контексте и в каком объекте мы находимся, или в какой момент структура начинает усложняться. Взгляните на следующий пример:
Классы и экземпляры классов
Классы используются, чтобы делать код более абстрактным и разделять их поведение. Не стоит объявлять функцию info несколько раз, как было указано в последнем примере. Поскольку классы и их экземпляры находятся в фактических объектах, то и действуют они одинаково. Однако следует упомянуть, что объявление this в конструкторе представляет собой предсказание на будущее, когда появится экземпляр.
Ошибка: вложенные вызовы функции
Иногда мы оказываемся не в том контексте, которого мы ожидали. Это может произойти при вызове функции внутри контекста другого объекта. Самый распространенный пример при использовании setTimeout или setInterval :
Угадайте, какое значение имеет coffee.amount ?
Все еще 120 . Изначально мы находились внутри объекта coffee , поскольку метод drink объявлен внутри него. Мы просто выполнили setTimeout и ничего больше. Дело именно в этом.
Как было сказано ранее, метод setTimeout на самом деле объявлен в объекте window . При вызове этого метода мы возвращаемся к контексту window . Это означает, что инструкции пытались изменить window.amount , но ничего не произошло из-за оператора if . Чтобы это исправить, нужно связать ( bind ) функции (пример ниже).
React
При работе с React подобные ситуации скоро останутся в прошлом, благодаря Hooks. Но на данный момент все еще необходимо связать ( bind ) все функции тем или иным способом (подробнее об этом позже).
Рассмотрим два простых компонента класса React:
При нажатии на кнопку, вынесенную классом Child , происходит ошибка. Почему? Поскольку React изменил контекст при вызове метода _getCoffee .
Я предполагаю, что React вызывает метод render для компонентов в другом контексте, с помощью вспомогательных классов или аналогичным образом (хотя придется копать глубже, чтобы узнать наверняка). Тем не менее this.state не определен, а мы пытаемся получить доступ к this.state.coffeeCount . Программа выдает нечто подобное Cannot read property coffeeCount of undefined .
Чтобы решить эту проблему, необходимо связать ( bind ) методы в классах при передаче их из компонента, в котором они определены.
Рассмотрим еще один базовый пример:
Мы передаем increaseCount из одного класса в другой. При вызове метода increaseCount в Viking контекст уже изменен, а this действительно указывает на Viking . Это означает, что increaseCount не будет работать так, как ожидалось.
Решение — bind
Лучшее решение в этой ситуации — связать ( bind ) методы, которые будут переданы из первоначального объекта или класса. Есть несколько способов связывания функций, но самый распространенный (даже в React) — это связать их в конструкторе. Нужно добавить эту строку в конструктор Battle :
Можно привязать любую функцию к любому контексту. Необязательно привязывать функцию к контексту, в котором она объявлена (хотя это самый распространенный случай). Вместо этого, можно привязать ее к другому контексту. С помощью bind всегда устанавливается контекст для объявления функции. Это означает, что все вызовы для этой функции получат связанный контекст, как и в случае с this . Существует еще два способа установки контекста.
Стрелочные функции `() => <>` автоматически привязывают функцию к контексту объявления.
Функции apply и call
Они обе выполняют одно и то же действие, различается лишь их синтаксис. В обоих случаях контекст передается в качестве первого аргумента. apply охватывает массив других аргументов, а при использовании call можно просто разделить другие аргументы запятыми. И что же они выполняют? Оба метода устанавливают контекст для одного определенного вызова функции. При вызове функции без метода call контекст устанавливается по умолчанию (или даже связанный контекст). Пример:
Угадайте, какой контекст у последнего вызова showType() ?
Вы правы, самый внешний контекст — window . Тем не менее type не определен ( undefined ), а window.type не существует
Вот и все. Надеюсь, вы разобрались в том, как использовать this в JavaScript.
О ключевом слове «this» языка JavaScript: особенности использования с пояснениями
Долгое время ключевое слово this оставалось для меня загадкой. Это мощный инструмент, но разобраться в нём нелегко.
С точки зрения Java, PHP или любого другого обычного языка this расценивается как экземпляр текущего объекта в методе класса, не больше и не меньше. Чаще всего его нельзя использовать вне метода, и этот подход не вызывает непонимания.
В JavaScript this — это текущий контекст исполнения функции. Поскольку функцию можно вызвать четырьмя способами:
- вызов функции: alert(‘Hello World!’) ,
- вызов метода: console.log(‘Hello World!’) ,
- вызов конструктора: new RegExp(‘\\d’) ,
- непрямой вызов: alert.call(undefined, ‘Hello World!’) ,
и каждый из них определяет свой контекст, поведение this слегка не соответствует ожиданиям начинающих разработчиков. Кроме того, strict mode также влияет на контекст исполнения.
Ключом к пониманию ключевого слова this является осознание принципов вызова функции и его влияния на контекст. В этой статье рассказывается про вызовы функций, влияние вызовов на this и типичные ловушки при идентификации контекста.
Прежде чем мы начнём, давайте познакомимся с несколькими терминами:
- Вызов — это исполнение кода тела функции. Например, вызовом функции parseInt будет parseInt(’15’) .
- Контекстом вызова является значение this в теле функции.
- Область видимости функции — это набор переменных, объектов и функций, к которым можно получить доступ из тела функции.
Содержание:
Вызов функции
Вызов функции совершается, когда за выражением, являющимся объектом функции, следуют открывающая скобка ( , разделённый запятыми список аргументов и закрывающая скобка ) , например, parseInt(’18’) . Выражение не может быть аксессором myObject.myFunction , который совершает вызов метода. Например, [1,5].join(‘,’) — это вызов не функции, а метода.
Простой пример вызова функции:
hello(‘World’) — это вызов функции: hello расценивается как объект функции, за которым в скобках следует аргумент ‘World’ .
Это тоже вызов функции: первая пара скобок (function(name) <. >) расценивается как объект функции, за которым в скобках следует аргумент: (‘World’) .
this при вызове функции
this — это глобальный объект при вызове функции
Глобальный объект определяется средой исполнения. В веб-браузере это объект window .
В вызове функции контекстом исполнения является глобальный объект. Давайте проверим контекст следующей функции:
Когда вызывается sum(15, 16) , JavaScript автоматически инициализирует this как глобальный объект, являющийся window в браузере.
Когда this используется вне области видимости какой-либо функции (самая внешняя область видимости: контекст глобального исполнения), он также относится к глобальному объекту:
this при вызове функции в strict mode
this принимает значение undefined при вызове функции в strict mode
Strict mode был введён в ECMAScript 5.1 и представляет собой более надёжную систему защиты и проверки ошибок. Для активации поместите директиву ‘use strict’ вверху тела функции. Этот режим влияет на контекст исполнения, заставляя this быть undefined . Контекст исполнения перестаёт быть глобальным объектом, в отличие от предыдущего случая.
Пример функции, запущенной в strict mode:
Когда multiply(2, 5) вызывается this становится undefined .
Strict mode активен не только в текущей области видимости, но и во всех вложенных:
‘use strict’ вставлена вверху тела execute , что активирует strict mode внутри её области видимости. Поскольку concat объявлена внутри области видимости execute , она наследует strict mode. И вызов concat(‘Hello’, ‘ World!’) приводит к тому, что this становится undefined .
Один файл JavaScript может содержать как «строгие», так и «нестрогие» функции. Поэтому возможно иметь в одном скрипте разные контексты исполнения для одного типа вызова:
Ловушка: this во внутренней функции
Обычной ошибкой при работе с вызовом функции является уверенность в том, что this во внутренней функции такой же, как и во внешней.
Вообще-то контекст внутренней функции зависит только от вызова, а не от контекста внешней функции.
Чтобы получить ожидаемый this , модифицируйте контекст внутренней функции при помощи непрямого вызова (используя .call() или .apply() , об этом позже) или создайте связанную функцию (используя .bind() , об этом тоже поговорим позже).
Следующий пример вычисляет сумму двух чисел:
numbers.sum() — это вызов метода объекта, поэтому контекстом sum является объект numbers . Функция calculate определена внутри sum , поэтому вы можете ожидать, что this — это объект numbers и в calculate() . Тем не менее, calculate() — это вызов функции, а не метода, и поэтому его this — это глобальный объект window или undefined в strict mode. Даже если контекстом внешней функции sum является объект numbers , у него здесь нет власти.
Результатом вызова numbers.sum() является NaN или ошибка TypeError: Cannot read property ‘numberA’ of undefined в strict mode. Точно не ожидаемый результат 5 + 10 = 15 , а всё потому, что calculate вызвана некорректно.
Для решения проблемы функция calculate должна быть исполнена в том же контексте, что и метод sum , чтобы получить доступ к значениям numberA и numberB . Это можно сделать при помощи метода .call() :
calculate.call(this) исполняет функцию calculate , но дополнительно модифицирует контекст в соответствии с первым параметром. Теперь this.numberA + this.numberB эквивалентно numbers.numberA + numbers.numberB и функция возвращает ожидаемый результат 5 + 10 = 15 .
Вызов метода
Метод — это функция, хранящаяся в объекте. Пример:
helloFunction — это метод в myObject . Для доступа к методу нужно использовать аксессор: myObject.helloFunction .
Вызов метода совершается, когда за выражением в виде аксессора, расценивающемся как объект функции, следует пара скобок и разделенный запятыми список аргументов между ними.
В прошлом примере myObject.helloFunction() — это вызов метода helloFunction объекта myObject . Также вызовами метода являются: [1, 2].join(‘,’) или /\s/.test(‘beautiful world’) .
Важно отличать вызов функции от вызова метода. Главным отличием является то, что для вызова метода необходим аксессор ( <expression>.functionProperty() или <expression>[‘functionProperty’]() ), а для вызова функции — нет ( <expression>() ).
this при вызове метода
this — это объект, которому принадлежит метод
При вызове метода, принадлежащего объекту, this становится этим объектом.
Давайте создадим объект, метод которого увеличивает число на 1:
Вызов calc.increment() сделает контекстом функции increment объект calc . Поэтому можно спокойно использовать this.num .
Объект JavaScript наследует метод своего прототипа. Когда вызывается метод, унаследованный от объекта, контекстом всё равно является сам объект:
Object.create() создаёт новый объект myDog и создаёт прототип. Объект myDog наследует метод sayName . Когда исполняется myDog.sayName() , myDog является контекстом исполнения.
В синтаксисе ECMAScript 6 class контекст вызова метода — тоже сам объект:
Ловушка: отделение метода от его объекта
Метод объекта можно переместить в отдельную переменную. При вызове метода с использованием этой переменной вы можете подумать, что this — это объект, в котором определён метод.
На самом деле, если метод вызван без объекта, происходит вызов функции, и this становится глобальным объектом window или undefined . Создание связанной функции исправляет контекст — им становится объект, в котором содержится метод.
Следующий пример создаёт конструктор Animal и его экземпляр — myCat . Затем через 1 секунду setTimeout() логирует информацию об объекте myCat :
Вы можете подумать, что setTimeout вызовет myCat.logInfo() , которая запишет информацию об объекте myCat . Но метод отделяется от объекта, когда передаётся в качестве параметра: setTimout(myCat.logInfo) , и через секунду происходит вызов функции. Когда logInfo вызывается как функция, this становится глобальным объектом или undefined (но не объектом myCat ), поэтому информация об объекте выводится некорректно.
Функцию можно связать с объектом, используя метод .bind() . Если отделённый метод связан с объектом myCat , проблема контекста решается:
myCat.logInfo.bind(myCat) возвращает новую функцию, исполняемую в точности как logInfo , но this которой остаётся myCat даже в случае вызова функции.
Вызов конструктора
Вызов конструктора совершается, когда за ключевым словом new следует выражение, расцениваемое как объект функции, и пара скобок с разделённым запятыми списком аргументов. Пример: new RegExp(‘\\d’) .
В этом примере объявляется функция Country , которая затем вызывается в качестве конструктора:
new Country(‘France’, false) — это вызов конструктора функции Country . Результатом исполнения является новые объект, чьё поле name равняется ‘France’ .
Если конструктор вызван без аргументов, скобки можно опустить: new Country .
Начиная с ECMAScript 6, JavaScript позволяет определять конструкторы ключевым словом class :
new City(‘Paris’) — это вызов конструктора. Инициализация объекта управляется специальным методом класса: constructor , this которого является только что созданным объектом.
Вызов конструктора создаёт новый пустой объект, наследующий свойства от прототипа конструктора. Ролью функции-конструктора является инициализация объекта. Как вы уже знаете, контекст этого типа вызова называется экземпляром. Это — тема следующей главы.
Когда перед аксессором myObject.myFunction идёт ключевое слово new , JavaScript совершит вызов конструктора, а не метода. Возьмём в качестве примера new myObject.myFunction() : сперва при помощи аксессора extractedFunction = myObject.myFunction функция извлекается, а затем вызывается как конструктор для создания нового объекта: new extractedFunction() .
this в вызове конструктора
this — это только что созданный объект
Контекстом вызова конструктора является только что созданный объект. Он используется для инициализации объекта данными из аргументом функции-конструктора.
Давайте проверим контекст в следующем примере:
new Foo() делает вызов конструктора с контекстом fooInstance . Объект инициализируется внутри Foo : this.property задаётся значением по умолчанию.
Тоже самое происходит при использовании class , только инициализация происходит в методе constructor :
Когда исполняется new Bar() , JavaScript создаёт пустой объект и делает его контекстом метода constructor . Теперь вы можете добавлять свойства, используя this : this.property = ‘Default Value’ .
Ловушка: как не забыть про new
Некоторые функции JavaScript создают экземпляры при вызове не только в качестве конструктора, но и функции. Например, RegExp :
При исполнении new RegExp(‘\\w+’) и RegExp(‘\\w+’) JavaScript создаёт эквивалентные объекты регулярных выражений.
Использование вызова функции для создания объектов потенциально опасно (если опустить фабричный метод), потому что некоторые конструкторы могут не инициализировать объект при отсутствии ключевого слова new .
Следующий пример иллюстрирует проблему:
Vehicle — это функция, задающая свойства type и wheelsCount объекту-контексту. При исполнении Vehicle(‘Car’, 4) возвращается объект car , обладающий корректными свойствами: car.type равен ‘Car’ а car.wheelsCount — 4 . Легко подумать, что всё работает как надо.
Тем не менее, this — это объект window при вызове функции, и Vehicle(‘Car’, 4) задаёт свойства объекта window — упс, что-то пошло не так. Новый объект не создан.
Обязательно используйте оператор new , когда ожидается вызов конструктора:
new Vehicle(‘Car’, 4) работает верно: новый объект создан и инициализирован, поскольку присутствует слово new .
В вызове функции добавлена верификация: this instanceof Vehicle , чтобы убедиться, что у контекста исполнения верный тип объекта. Если this — не Vehicle , генерируется ошибка. Таким образом, если исполняется Vehicle(‘Broken Car’, 3) (без new ), то выбрасывается исключение: Error: Incorrect invocation .
Непрямой вызов
Непрямой вызов производится, когда функция вызывается методами .call() или .apply() .
Функции в JavaScript — объекты первого класса, то есть функция — это объект типа Function .
Из списка методов этой функции два, .call() и .apply() , используются для вызова функции с настраиваемым контекстом:
- Метод .call(thisArg[, arg1[, arg2[, . ]]]) принимает в качестве первого аргумента thisArg контекст вызова, а список аргументов arg1, arg2, . передаётся вызываемой функции.
- Метод .apply(thisArg, [args]) принимает в качестве первого аргумента thisArg контекст вызова, а array-like объект [args] передаётся вызываемой функции в качестве аргумента.
Следующий пример демонстрирует непрямой вызов:
increment.call() и increment.apply() оба вызывают функцию-инкремент с аргументом 10 .
Главным отличием между ними является то, что .call() принимает список аргументов, например, myFunction.call(thisValue, ‘value1’, ‘value2’) , а .apply() принимает эти значения в виде array-like объекта: myFunction.apply(thisValue, [‘value1’, ‘value2’]) .
this при непрямом вызове
this — это первый аргумент .call() или .apply()
Очевидно, что при непрямом вызове this — значение, передаваемое .call() или .apply() в качестве первого аргумента. Пример:
Непрямой вызов может пригодиться, когда функцию нужно вызвать в особом контексте, например, решить проблему при вызове функции, где this — всегда window или undefined . Его также можно использовать для симуляции вызова метода объекта.
Ещё одним примером использования является создание иерархии классов в ES5 для вызова родительского конструктора:
Runner.call(this, name) в Rabbit создаёт непрямой вызов родительской функции для инициализации объекта.
Связанная функция
Связанная функция — это функция, связанная с объектом. Обычно она создаётся из обычной функции при помощи метода .bind() . У двух функций совпадают тела и области видимости, но различаются контексты.
Метод .bind(thisArg[, arg1[, arg2[, . ]]]) принимает в качестве первого аргумента thisArg контекст вызова связанной функции, а необязательный список аргументов arg1, arg2, . передаётся вызываемой функции. Он возвращает новую функцию, связанную с thisArg .
Следующий код создаёт связанную функцию и вызывает её:
multiply.bind(2) возвращает новый объект функции double , который связан с числом 2 . Код и область видимости у multiply и double совпадают.
В отличие от методов .apply() и .call() , сразу вызывающих функцию, метод .bind() возвращает новую функцию, которую впоследствии нужно будет вызвать с уже заданным this .
this в связанной функции
Ролью .bind() является создание новой функции, чей вызов будет иметь контекст, заданный в первом аргументе .bind() . Это — мощный инструмент, позволяющий создавать функции с заранее определённым значением this .
Давайте посмотрим, как настроить this связанной функции:
numbers.getNumbers.bind(numbers) возвращает функцию boundGetNumbers , которая связана с объектом numbers . Затем boundGetNumbers() вызывается с this , равным numbers , и возвращает корректный объект.
Функцию numbers.getNumbers можно извлечь в переменную simpleGetNumbers и без связывания. При дальнейшем вызове функции simpleGetNumbers() задаёт this как window или undefined , а не numbers . В этом случае simpleGetNumbers() не вернет корректное значение.
.bind() создаёт перманентную контекстную ссылку и хранит её. Связанная функция не может изменить контекст, используя .call() или .apply() с другим контекстом — даже повторное связывание не даст эффекта.
Только вызов связанной функции как конструктора может изменить контекст, но это не рекомендуется (используйте нормальные функции).
В следующем примере сперва объявляется связанная функция, а затем производится попытка изменить контекст:
Только new one() изменяет контекст связанной функции, в остальных типах вызова this всегда равен 1 .
Стрелочная функция
Стрелочная функция нужна для более короткой формы объявления функции и лексического связывания контекста.
Её можно использовать следующим образом:
Стрелочные функции используют облегчённый синтаксис, убирая ключевое слово function . Можно даже опустить return , когда у функции есть лишь одно выражение.
Стрелочная функция анонимна, что означает, что её свойство name — пустая строка » . Таким образом, у неё нет лексического имени, которое нужно для рекурсии и управления хэндлерами.
Кроме того, она не предоставляет объект arguments , в отличие от обычной функции. Тем не менее, это можно исправить, используя rest-параметры ES6:
this в стрелочной функции
this — это контекст, в котором определена стрелочная функция
Стрелочная функция не создаёт свой контекст исполнения, а заимствует this из внешней функции, в которой она определена.
Следующий пример показывает прозрачность контекста:
setTimeout вызывает стрелочную функцию в том же контексте (метод myPoint ), что и метод log() . Как мы видим, стрелочная функция «наследует» контекст той функции, в которой определена.
Если попробовать использовать в этом примере обычную функцию, она создаст свой контекст ( window или undefined ). Поэтому для того, чтобы код работал корректно, нужно вручную привязать контекст: setTimeout(function() <. >.bind(this)) . Это громоздко, поэтому проще использовать стрелочную функцию.
Если стрелочная функция определена вне всех функций, её контекст — глобальный объект:
Стрелочная функция связывается с лексическим контекстом раз и навсегда. this нельзя изменить даже при помощи метод смены контекста:
Функция, вызываемая непрямым образом с использованием .call(numbers) , задаёт this значение numbers . Стрелочная функция get также получает numbers в качестве this , поскольку принимает контекст лексически. Неважно, как вызывается get , её контекстом всегда будет numbers . Непрямой вызов с другим контекстом (используя .call() или .apply() ), повторное связывание (с использованием .bind() ) не принесут эффекта.
Стрелочную функцию нельзя использовать в качестве конструктора. Если вызвать new get() , JavaScript выбросит ошибку: TypeError: get is not a constructor .
Ловушка: определение метода стрелочной функцией
Вы можете захотеть использовать стрелочную функцию для объявления метода. Справедливо: их объявления гораздо короче по сравнению с обычным выражением: (param) => <. >вместо function(param) <..>.
В этом примере демонстрируется определение метода format() класса Period с использованием стрелочной функции:
Так как format — стрелочная функция, определённая в глобальном контексте, её this — это объект window . Даже если format исполняется в качестве метода объекта walkPeriod.format() , window остаётся контекстом вызова. Так происходит, потому что стрелочная функция имеет статический контекст, не изменяемый другими типами вызовов.
this — это window , поэтому this.hours и this.minutes становятся undefined . Метод возвращает строку ‘undefined hours and undefined minutes’ , что не является желаемым результатом.
Функциональное выражение решает проблему, поскольку обычная функция изменяет свой контекст в зависимости от вызова:
walkPeriod.format() — это вызов метода с контекстом walkPeriod . this.hours принимает значение 2 , а this.minutes — 30 , поэтому метод возвращает корректный результат: ‘2 hours and 30 minutes’ .
Заключение
Поскольку вызов функции имеет наибольшее влияние на this , отныне не спрашивайте:
а спрашивайте:
А в случае со стрелочной функцией спросите:
Каков this там, где объявлена стрелочная функция?
Ключевое слово this в JavaScript для начинающих
Автор материала, перевод которого мы сегодня публикуем, говорит, что когда она работала в сфере бухучёта, там применялись понятные термины, значения которых легко найти в словаре. А вот занявшись программированием, и, в частности, JavaScript, она начала сталкиваться с такими понятиями, определения которых в словарях уже не найти. Например, это касается ключевого слова this . Она вспоминает то время, когда познакомилась с JS-объектами и функциями-конструкторами, в которых использовалось это ключевое слово, но добраться до его точного смысла оказалось не так уж и просто. Она полагает, что подобные проблемы встают и перед другими новичками, особенно перед теми, кто раньше программированием не занимался. Тем, кто хочет изучить JavaScript, в любом случае придётся разобраться с this . Этот материал направлен на то, чтобы всем желающим в этом помочь.
Что такое this?
Предлагаю вашему вниманию моё собственное определение ключевого слова this . This — это ключевое слово, используемое в JavaScript, которое имеет особое значение, зависящее от контекста в котором оно применяется.
Причина, по которой this вызывает столько путаницы у новичков, заключается в том, что контекст this меняется в зависимости от его использования.
This можно считать динамическим ключевым словом. Мне нравится, как понятие «контекст» раскрыто в этой статье Райана Морра. По его словам, контекст всегда является значением ключевого слова this , которое ссылается на объект, «владеющий» кодом, выполняемым в текущий момент. Однако, тот контекст, который имеет отношение к this , это не то же самое, что контекст выполнения.
Итак, когда мы пользуемся ключевым словом this , мы, на самом деле, обращаемся с его помощью к некоему объекту. Поговорим о том, что это за объект, рассмотрев несколько примеров.
Ситуации, когда this указывает на объект window
Если вы попытаетесь обратиться к ключевому слову this в глобальной области видимости, оно будет привязано к глобальному контексту, то есть — к объекту window в браузере.
При использовании функций, которые имеются в глобальном контексте (это отличает их от методов объектов) ключевое слово this в них будет указывать на объект window .
Попробуйте выполнить этот код, например, в консоли браузера:
Использование this внутри объекта
Когда this используется внутри объекта, это ключевое слово ссылается на сам объект. Рассмотрим пример. Предположим, вы создали объект dog с методами и обратились в одном из его методов к this . Когда this используется внутри этого метода, это ключевое слово олицетворяет объект dog .
This и вложенные объекты
Применение this во вложенных объектах может создать некоторую путаницу. В подобных ситуациях стоит помнить о том, что ключевое слово this относиться к тому объекту, в методе которого оно используется. Рассмотрим пример.
Особенности стрелочных функций
Стрелочные функции ведут себя не так, как обычные функции. Вспомните: при обращении к this в методе объекта, этому ключевому слову соответствует объект, которому принадлежит метод. Однако это не относится к стрелочным функциям. Вместо этого, this в таких функциях относится к глобальному контексту (к объекту window ). Рассмотрим следующий код, который можно запустить в консоли браузера.
Если, озадачившись рассматриваемым вопросом, заглянуть на MDN, там можно найти сведения о том, что стрелочные функции имеют более короткую форму записи, чем функциональные выражения и не привязаны к собственным сущностям this , arguments , super или new.target . Стрелочные функции лучше всего подходят для использования их в роли обычных функций, а не методов объектов, их нельзя использовать в роли конструкторов.
Прислушаемся к MDN и не будем использовать стрелочные функции в качестве методов объектов.
Использование this в обычных функциях
Когда обычная функция находится в глобальной области видимости, то ключевое слово this , использованное в ней, будет привязано к объекту window . Ниже приведён пример, в котором функцию test можно рассматривать в виде метода объекта window .
Однако если функция выполняется в строгом режиме, то в this будет записано undefined , так как в этом режиме запрещены привязки по умолчанию. Попробуйте запустить следующий пример в консоли браузера.
Обращение к this из функции, которая была объявлена за пределами объекта, а потом назначена в качестве его метода
Рассмотрим пример с уже известным нам объектом dog . В качестве метода этого объекта можно назначить функцию chase , объявленную за его пределами. Тут в объекте dog никаких методов не было, до тех пор, пока мы не создали метод foo , которому назначена функция chase . Если теперь вызвать метод dog.foo , то будет вызвана функция chase . При этом ключевое слово this , к которому обращаются в этой функции, указывает на объект dog . А функция chase , при попытке её вызова как самостоятельной функции, будет вести себя неправильно, так как при таком подходе this будет указывать на глобальный объект, в котором нет тех свойств, к которым мы, в этой функции, обращаемся через this .
Ключевое слово new и this
Ключевое слово this находит применение в функциях-конструкторах, используемых для создания объектов, так как оно позволяет, универсальным образом, работать со множеством объектов, создаваемых с помощью такой функции. В JavaScript есть и стандартные функции-конструкторы, с помощью которых, например, можно создавать объекты типа Number или String . Подобные функции, определяемые программистом самостоятельно, позволяют ему создавать объекты, состав свойств и методов которых задаётся им самим.
Как вы уже поняли, мне нравятся собаки, поэтому опишем функцию-конструктор для создания объектов типа Dog , содержащих некоторые свойства и методы.
Когда функцию-конструктор вызывают с использованием ключевого слова new , this в ней указывает на новый объект, который, с помощью конструктора, снабжают свойствами и методами.
Вот как можно работать со стандартными конструкторами JavaScript.
Теперь поработаем с только что созданной функцией-конструктором Dog .
Вот ещё один пример использования функций-конструкторов.
О важности ключевого слова new
При вызове функции-конструктора с использованием ключевого слова new ключевое слово this указывает на новый объект, который, после некоторой работы над ним, будет возвращён из этой функции. Ключевое слово this в данной ситуации весьма важно. Почему? Всё дело в том, что с его помощью можно, используя единственную функцию-конструктор, создавать множество однотипных объектов.
Это позволяет нам масштабировать приложение и сокращать дублирование кода. Для того чтобы понять важность этого механизма, подумайте о том, как устроены учётные записи в социальных сетях. Каждая учётная запись может представлять собой экземпляр объекта, создаваемый с помощью функции-конструктора Friend . Каждый такой объект можно заполнять уникальными данными о пользователе. Рассмотрим следующий код.
Итоги
На самом деле, особенности использования ключевого слова this в JavaScript не ограничиваются вышеописанными примерами. Так, в череду этих примеров можно было бы включить использование функций call , apply и bind . Так как материал этот рассчитан на начинающих и ориентирован на разъяснение основ, мы их здесь не касаемся. Однако если сейчас у вас сформировалось начальное понимание this , то и с этими методами вы вполне сможете разобраться. Главное — помните о том, что если что-то с первого раза понять не удаётся, не прекращайте учиться, практикуйтесь, читайте материалы по интересующей вас теме. В одном из них вам обязательно попадётся нечто такое (какая-то удачная фраза, например), что поможет понять то, что раньше понять не удавалось.
Уважаемые читатели! Возникали ли у вас сложности с пониманием ключевого слова this в JavaScript?