В JavaScript объекты это составной тип данных, его значение передается по ссылке. Другими словами, когда мы передаем объект в функцию как параметр или где угодно можем поменять его свойства. Используя инструкцию состоящую из выражения переменной, хранящей ссылку, а также операторов точка и присваивания. После этого другие инструкции, которые работают или будут работать с этой переменной/параметром, по ссылке получат изменение свойства.
Часто такое поведение искажает данные пользователя, приводит к ошибкам и является нежелательным.
Поиск источника таких нежелательных изменений свойств может занять долгое время: так программа может быть уже большой и состоять из сотни тысяч инструкций.
Давайте рассмотрим простой пример.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Debug property mutation example</title> <script> const user = { firstName: 'Vasilij', middleName: 'Alibabaevich', lastName: 'Radner', aka: 'Alibaba', getFullName() { return `${this.lastName} ${this.firstName.slice(0, 1)}. ${this.middleName.slice(0, 1)}.` } }; </script> <script src="object-property-mutation.js" type="application/javascript"></script> </head> </head> <body> <script> Promise.resolve(user).then(user.getFullName.bind(user)).then(console.log); </script> </body> </html>
Сейчас веб программа не работает, так как в консоли есть ошибка и нет вывода ФИО.
Читаем самое верхнее сообщение в консоли:
Uncaught (in promise) TypeError: Cannot read property 'slice' of undefined
Не обработано (в промис) ошибка типа: не могу прочитать свойство slice от неопределено.
Нажимаем на ссылку и переходим к месту ошибки.
getFullName() { return `${this.lastName} ${this.firstName.slice(0, 1)}. ${this.middleName.slice(0, 1)}.` }
Видим, что ошибочное выражение
this.firstName.slice(0, 1)
состоит из четырех операторов:
- два оператора точка
- один оператор-разделитель запятая
- один оператор группировки — пара круглых скобок
Давайте читать инструкцию. Первым вычисляется левое выражение
this.firstName
Оно состоит из оператора точка, слева первичное выражения this и идентификатора firstName справа. Результатом этого выражения будет undefined. Выполнение следующего оператора точка вызывает ошибку. Так как оператор точка работает только с объектными типами, его выполнение от undefined приводит к ошибке — не могу получить свойство slice от undefined.
Получается, что где-то этому свойству было присвоено значение undefined…
Чтобы решить эту проблему попробуем пойти от обратного. Воспользуемся инструментом отладки остановка на исключении. Двигаясь от места ошибки по стеку вызовов вниз, попробуем перейти к инструкции, которая изменила свойство.
Выбираем инструмент остановка на исключении
Видим, что в стеке всего два вызова. Переходим в предыдущий вызов.
Видим, что нет явной инструкции, которая изменяют свойство firstName.
Делаем вывод, что изменение не происходит в этом стеке вызовов.
Как вам такое?
И как найти негодяя который изменил свойство моего объекта?
Пожалуйста, напишите в комментах как бы вы нашли его?
Ребята, кто со мной работает и кому я это рассказал, напишите плиз звездочку в комменте.
Мне очень интересно узнать, как другие специалисты JavaScript решают подобные проблемы.
Знаете, когда я встретился с этим поведением JavaScript впервые, я потратил пару часов на расследование и выдрал клок волос с челки…
Отключаем инструмент пауза на исключении.
Итак, вот наш новый план мы определим в объекте user свойство firstName, используя геттер и сеттер.
В сеттер добавим инструкцию отладки, используя оператор debugger и оператор точка с запятой.
const user = { _firstName: 'Vasilij', set firstName(value) { debugger; this._firstName = value; }, get firstName() {return this._firstName}, middleName: 'Alibabaevich', lastName: 'Radner', aka: 'Alibaba', getFullName() { return `${this.lastName} ${this.firstName.slice(0, 1)}. ${this.middleName.slice(0, 1)}.`; } };
Двигаясь дальше по стеку вызовов, найдем инструкцию, которая меняет свойство firstName.
Отладчик остановился в сеттере до того, как новое значение будет записано в объект.
Видим, что значение параметра value undefined.
Теперь, используя стек вызовов мы легко переходим в предыдущий вызов.
иииии, победааааа ура.
Есть еще более простой способ решить эту проблему, используя инструмент отладки остановка на исключении.
Вот наш новый план: сделаем объект user не объектом и, используя инструмент остановку по исключению, легко попадем к ошибочной инструкции.
Так как мы знаем, что при попытке получения свойства от undefined возникает ошибка.
Включаем инструмент остановка на исключении, присваиваем переменной user значение undefined.
const user = undefined;
Мы снова остановились в месте искажения свойства firstName.
Это все, что я хотел вам сегодня рассказать об отладке нежелательных изменений у объектов.
Спасибо за прочтение статьи. Ставьте лайки, подписывайтесь на канал, делитесь этим видео и статьей с друзьями, всего хорошего.
github.com/NVBespalov/js-lessons/tree/error/property-mutation
ссылка на оригинал статьи https://habr.com/ru/post/481824/
Добавить комментарий