Потеря this в конструкторе

Обновлено: 02.05.2024

Ключевое слово this

Пример первый — у переменной экземпляра и метода одинаковые имена

Допустим, у нас есть класс Human , для которого определено поле «имя»: - 2" width="462" />
Давайте для переменной name создадим сеттер (setter вполне рабочий и никакого подвоха здесь нет): - 3" width="700" />
Обратите внимание, что в метод (сеттер) setName мы передаем переменную String newName . Мы ввели новую переменную и (в общем-то) могли назвать ее как угодно ведь она будет видна только в пределах метода setName . Обратите внимание, что в сеттере есть одна строка: То есть по факту мы ввели новую переменную newName и присвоили ее уже существующей в классе переменной name . Многим программистом казалось это странным, — вводить переменную с новым именем, если в итоге речь идет об одном и том же. Об имени в классе Human . Поэтому, разработчики языка задумались о том, чтобы удобно сделать использование одного имени переменной. Другими словами, зачем иметь два имени для переменной, обозначающей одно и то же. То есть хотелось бы сделать как-то так: - 4" width="700" />
Но в этом случае возникает проблема. У нас теперь две переменные, которые называются одинаково. Один String name принадлежит классу Human , а другой String name его методу setName . Поэтому Java – машина не знает, какую переменную вы имеете ввиду, когда пишете строку в сеттере: Java берет самую близкую – name из метода setName :

Ключевое слово this <в примерах></p>
<p>- 5

и получается, что вы просто присваиваете значение переменной name из этого метода, ей же. Что конечно не имеет никакого смысла. Поэтому нужен был какой-то способ, чтобы отличить переменную name из класса Human , от переменной name из метода setName .Для решения этой проблемы и было введено ключевое слово this , которое в данном случае укажет, что нужно вызывать переменную не метода, а класса Human :

Ключевое слово this <в примерах></p>
<p>- 6

То есть this сошлется на вызвавший объект, как было сказано в начале статьи. В результате чего имя человека через сеттер setName будет установлено создаваемому объекту. Ниже приведен программный код без использования ключевого слова this . В коде создается объект класса Human и присваивается ему имя:

А ниже программный код с ключевым словом this : Таким образом, здесь this позволяет не вводить новые переменные для обозначения одного и того же, что позволяет сделать код менее «перегруженным» дополнительными переменными.

Пример второй — Применение this для явного вызова конструктора

  • вызови this (этот) конструктор, который имеет два параметра.
  • и добавить недостающую переменную.

Ключевое слово this <в примерах></p>
<p>- 9

И, снова здравствуйте.
Буду краток, при объявлении переменных и не только, через "this" происходит потеря, очевидно, приводящая к ошибке NullPointerException.
Собственно, NetBeans подсказал в чем может быть проблема, выдав предупреждение на этом отрезке кода:

А вопрос вот в чем: Во-первых, можно ли устранить эту потерю? И, Во-вторых, стоит ли вообще её устранять, или же, стоит поискать проблему в чем то другом?

ps Уже посещал гугл, кроме пары аглицких сайтов ничего по своему вопросу не нашел, да и под мой случай решения представленные там не сильно подходят.

Потеря this в конструкторе
пишу код, и в одном месте возникает жёлтенький треугольничек, с предупреждением что "потеря this в.

Потеря this в конструкторе
Доброго времени суток Имеется 5 файлов кода : import javafx.animation.AnimationTimer;.

Ошибка в конструкторе
Почему пишет что отсутствуют нужные параметры конструктора? class Aircraft < public.

QTime в конструкторе
Здравствуйте. Подскажите как можно использовать QTime в качестве параметра в конструкторе.

Решение

aldekotan, утечка this - это потенциальная проблема. Может привести к всяким нехорошим вещам в будущем.
Избавиться можно так: присвоить Main.var_5b в методе, который вызывает конструктор.

Не совсем понял способ решения. Подумав некоторое время, в методе Main() сделал вызов void Main2, в котором и провел операции с this. Предупреждений больше нет.

aldekotan, Проблема при таком решении никуда не делась. Вы просто обманули статический анализатор.

Вы же где-то вызываете конструктор Main, так? Ведь можно добавить эту строчку в код вызывающего метода.
Как-то так:

aldekotan, Проблема при таком решении никуда не делась. Вы просто обманули статический анализатор.

Вы же где-то вызываете конструктор Main, так? Ведь можно добавить эту строчку в код вызывающего метода.
Как-то так:

turbanoff, ваши слова подтвердились. Та же ошибка в том же месте.
Вот места кода инициализации, и непосредственно Main во всей красе. Черт его знает, может это чем то поможет

Добавлено через 23 часа 14 минут
Если мне не изменяет память, метод Main() вызывается первым сам по себе. Собственно как и класс Main. Разве нет?
Чем теперь заменять this, понятия не имею. Пробовал инициализировать другие объекты, но, все без толку.

Проще всего переделать так, чтобы не нужно было сохранять ссылку в var_5b. Зачем вы её туда сохраняете?

Проще всего переделать так, чтобы не нужно было сохранять ссылку в var_5b. Зачем вы её туда сохраняете?

Лично я, не очень представляю зачем. Однако если просто не сохранять её, приложение крашится с ошибкой.

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

Взял старые исходники (оригинал, так сказать), не стал исправлять участки кода из-за предупреждений, просто исправил критические ошибки, и, приложение заработало без запинки. Из чего сделал вывод, что NPE вызывался не из-за "потери this".А по какой-то другой причине. Однако, искать эту самую причину, нет ни малейшего желания.
Здесь принято закрывать темы? Или нет? Так или иначе, вопрос исчерпан


RGB в конструкторе
Коллеги доброго! Как можно из режима конструктора задать цвет для компонента из RGB? В реал тайме.

Ошибка в конструкторе
Написал задачу, но она не работает. Условие: Разработать класс Элементарная частица с элементами.

Ошибка в конструкторе
Ну что за ошибка? после вызова конструктора? не могу понять.:-| Плиз, помогите разобраться! .

О конструкторе копирования
Доброго времени суток всем присутствующим. Интересует маленький вопрос. В 15-й строке происходит.

Привет, Хабр! Представляю вашему вниманию перевод статьи «What to do when “this” loses context» автора Cristi Salcescu.

Лучший способ избежать потери контекста this – не использовать this. Однако, это не всегда возможно. Например, мы работаем с чужим кодом или библиотекой, которые используют this.

Литерал объекта, функция-конструктор, конструктор объектов класса в системе прототипов. Псевдопараметр this используется в системе прототипирования для того, чтобы дать доступ к свойствам объекта.

Давайте рассмотрим несколько случаев.

Вложенные функции (Nested Functions)

this теряет ссылку на контекст внутри вложенных функций.


У метода doSomething() две вложенные функции: doAnotherthing() и log(). При вызове service.doSomething(), this теряет ссылку на контекст во вложенной функции.

Один из способов решения проблемы – метод bind(). Взгляните на следующий код:


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

function doAnotherThing()< /*…*/>.bind(this) создает версию функции doAnotherThing(), которая берет значение this из doSomething().

that/self

Другой вариант – объявить и использовать новую переменную that/self, которая будет хранить значение this из метода doSomething().


Мы должны объявлять let that = this во всех методах, использующих this во вложенных функциях.

Стрелочные функции (Arrow function)

Стрелочная функция даёт нам еще один способ решения этой проблемы.


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

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

Ниже представлен тот же код с функцией, выраженной через имя переменной:

Функции обратного вызова (Method as callback)

this теряет ссылку на контекст при использовании метода в качестве функции обратного вызова. Посмотрим на следующий класс:


Давайте разберем ситуации, в которых метод service.doSomething() используется как коллбэк-функция.


Во всех случаях выше this теряет ссылку на контекст.

Мы можем использовать bind() для решения этой проблемы. Ниже приведен код этого варианта:

Стрелочная функция

Еще один способ – создание стрелочной функции, которая вызывает service.doSomething().

React-компоненты (React Components)

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


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

Не использовать “this"

Нет this — нет проблем с потерей контекста. Объекты могут создаваться с помощью фабричных функций (factory functions). Посмотрите на этот пример:


Контекст остается если использовать метод в качестве коллбэка.

Заключение

this теряет ссылку на контекст в различных ситуациях.
bind(), использование переменной that/self и стрелочные функции — это способы решения проблем с контекстом.

Фабричные функции дают возможность создавать объекты без использования this.

Эту статью я пишу для своих личных нужд. Планируется, что она будет содержать в себе ответы на все вопросы, которые мне задают студенты на эту тему. Если она пригодится кому-то ещё — здорово.

image

Введение

Ключевое слово this — одна из наиболее запутывающих особенностей языка JavaScript. Пришедшее из Java, оно было призвано помочь в реализации ООП. Я какое-то время писал на Java, и должен сказать, что за это время у меня, может быть, один раз возникло сомнение, чему равняется this в конкретном месте кода. В JavaScript такие сомнения могут возникать каждый день — по крайней мере, до того момента, как выучишь несколько простых, но неочевидных правил.

Заблуждения о this

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

this — это лексический контекст.

Такое впечатление часто возникает у новичков. Им кажется, что this — это объект, в котором, как свойства, хранятся все переменные в данной области видимости. Это заблуждение происходит из того, что в одном конкретном случае это, грубо говоря, так. Если мы находимся на самом верхнем уровне, то this равняется window (в случае обычного скрипта в браузере). А как мы знаем, все переменные, объявленные на верхнем уровне, доступны как свойства window .

В общем случае это неправда. Это легко проверить.


this — это объект, которому принадлежит метод

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


Так какой же из этих объектов будет её this'ом?

Важно! Даже если объект создан с помощью классов ES6, это совершенно не гарантирует, что у его метода всегда будет нужный this . Пусть вас не вводит в заблуждение сходство с классами из «нормальных» языков.

this — это джедайская техника, которую, изучив, нужно использовать везде

По возможности лучше избегать употребления this . Единственный случай, когда оно полностью обосновано — это ООП. В прочих местах употребление this — это не то чтобы зло, но, как правило, излишество, запутывающее код. По сути this — это дополнительный, «минус первый» аргумент функции, который передаётся туда сложным, неочевидным новичку путём. С высокой вероятностью его можно заместить обычным аргументом, и станет только лучше.

Как определить значение this

Здесь я постараюсь дать строгий и лаконичный алгоритм, с помощью которого даже неискушённый кодер сумеет понять, чему равняется this в его конкретном случае. Более многословные пояснения буду спрятаны под спойлеры, дабы не захламлять визуальное пространство.

    Мы находимся внутри функции?

  • Да: смотрим следующий пункт.
  • Нет: this равен глобальному объекту.

В коде верхнего уровня (не находящемся внутри никакой функции) this всегда ссылается на глобальный объект. В случае обычного скрипта в браузере это — объект window . Но вообще случаи бывают разные.

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

Одна из основных особенностей стрелочных функций — это так называемый «лексический this ». Это значит, что значение this в стрелочной функции определяется исключительно тем, где (в каком лексическом контексте) она была создана, и никак не зависит от того, как впоследствии она была вызвана. Иногда это не то, что нам нужно, но чаще всего это делает стрелочные функции очень удобными и предсказуемыми.

  • Да: this ссылается на новый объект, находящийся «в процессе конструкции».
  • Нет: смотрим следующий пункт.

Пожалуй, стоит отдельно оговорить случай, когда речь идёт о конструкторе унаследованного ES6-класса. Тогда до вызова super() значения у this нет (обращение к нему вызовет ошибку), а после вызова super() он равняется новому объекту родительского класса.

  • Да: значение this равняется значению первого аргумента, который мы передали в метод bind при создании данной функции.
  • Нет: смотрим следующий пункт.

Метод bind создаёт копию функции, зафиксировав для неё this и, опционально, несколько первых аргументов. На самом деле при этом создаётся не просто копия функции, а, цитирую, «экзотический объект BoundFunction». Экзотичность его проявляется, в частности, в том, что повторным вызовом bind мы уже не сможем изменить this . Поэтому, строго говоря, ответ в этом пункте надо было сформулировать так: если да, то this равняется первому аргументу первого вызова bind , который привёл к созданию данной функции.

  • Да: одному Господу известно, чему будет равен this при её вызове. Идите читать документацию по той штуке, которая её станет вызывать.
  • Нет: смотрим следующий пункт.

У не стрелочной и не связанной (bound) функции значение this зависит от обстоятельств, в которых она была вызвана. Если вы не вызываете её лично, а передаёте куда-то, то в качестве this может быть или не быть подставлено неизвестное вам значение.

  • Да: в таком случае this равняется первому аргументу, переданному соответствующему методу.
  • Нет: смотрим следующий пункт.

Ещё один способ явно задать this . Точнее, два. Однако в плане this разницы между apply и call нет никакой, разница только в том, как передаются остальные аргументы.

  • Да: this равняется вышеупомянутому объекту.
  • Нет: смотрим следующий пункт.

Собственно, из этого механизма (а также — из опыта работы с другими языками) растут ноги у убеждения, что " this — это объект, чей метод мы вызвали". Пожалуй, я просто напишу код.

  • Да: this равняется undefined .
  • Нет: this равен глобальному объекту.

Если мы дошли до этого пункта, значит, this не задан ни одним из механизмов, которые позволяют его задать. Существуют различные заблуждения относительно того, как ещё может передаваться this . Например, на собеседованиях мне часто говорят вот такую вещь:


Или, как я уже говорил в секции «заблуждения», многие считают, что если функция является методом объекта, созданного с помощью классов ES6, то уж в ней-то this всегда будет равен этому объекту. Это тоже неправда.

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

Исторически в качестве «дефолтного» this в такие функции передавался глобальный объект. Позже этот подход был признан небезопасным. В ES5 появился строгий режим, исправляющий многие проблемы более ранних версий ECMAScript. Он включается директивой 'use strict' в начале файла или функции. В таком режиме «дефолтное» значение this — это undefined .

В ES6 модулях строгий режим включен по умолчанию.

Также существуют другие механизмы включения строгого режима, например, в NodeJS строгий режим для всех файлов можно включить флагом --use-strict .

P.S. Пользователь Aingis подсказал, что в случае использования конструкции with объект, переданный в неё в качестве контекста, подменяет собой глобальный объект. Пожалуй, я не стану вносить это в свой классификатор, потому что шанс встретить with в 2019+ году довольно мал. Но в любом случае это интересный момент.

P.P.S. Пользователь rqrqrqrq подсказал, что у new выше приоритет, чем у bind . Соответствующая правка в классификатор уже внесена. Спасибо!

Автор материала, перевод которого мы сегодня публикуем, говорит, что когда она работала в сфере бухучёта, там применялись понятные термины, значения которых легко найти в словаре. А вот занявшись программированием, и, в частности, 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 , то и с этими методами вы вполне сможете разобраться. Главное — помните о том, что если что-то с первого раза понять не удаётся, не прекращайте учиться, практикуйтесь, читайте материалы по интересующей вас теме. В одном из них вам обязательно попадётся нечто такое (какая-то удачная фраза, например), что поможет понять то, что раньше понять не удавалось.

Читайте также: