Быстрый поиск источника нежелательных мутации свойства объекта

от автора

Привет! Cегодня расскажу, как можно с помощью отладчика решить, на мой взгляд, нетривиальную проблему JavaScript.

В 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>  

Сейчас веб программа не работает, так как в консоли есть ошибка и нет вывода ФИО.

image

Читаем самое верхнее сообщение в консоли:

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)

состоит из четырех операторов:

  1. два оператора точка
  2. один оператор-разделитель запятая
  3. один оператор группировки — пара круглых скобок

Давайте читать инструкцию. Первым вычисляется левое выражение

this.firstName

Оно состоит из оператора точка, слева первичное выражения this и идентификатора firstName справа. Результатом этого выражения будет undefined. Выполнение следующего оператора точка вызывает ошибку. Так как оператор точка работает только с объектными типами, его выполнение от undefined приводит к ошибке — не могу получить свойство slice от undefined.

Получается, что где-то этому свойству было присвоено значение undefined…

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

Выбираем инструмент остановка на исключении

image

Видим, что в стеке всего два вызова. Переходим в предыдущий вызов.

image

Видим, что нет явной инструкции, которая изменяют свойство firstName.

image

Делаем вывод, что изменение не происходит в этом стеке вызовов.

Как вам такое?

И как найти негодяя который изменил свойство моего объекта?

Пожалуйста, напишите в комментах как бы вы нашли его?
Ребята, кто со мной работает и кому я это рассказал, напишите плиз звездочку в комменте.

Мне очень интересно узнать, как другие специалисты 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.

Отладчик остановился в сеттере до того, как новое значение будет записано в объект.

image

Видим, что значение параметра value undefined.

Теперь, используя стек вызовов мы легко переходим в предыдущий вызов.

image

иииии, победааааа ура.

Есть еще более простой способ решить эту проблему, используя инструмент отладки остановка на исключении.

Вот наш новый план: сделаем объект user не объектом и, используя инструмент остановку по исключению, легко попадем к ошибочной инструкции.

Так как мы знаем, что при попытке получения свойства от undefined возникает ошибка.

Включаем инструмент остановка на исключении, присваиваем переменной user значение undefined.

    const user = undefined; 

Мы снова остановились в месте искажения свойства firstName.

image

Это все, что я хотел вам сегодня рассказать об отладке нежелательных изменений у объектов.

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

github.com/NVBespalov/js-lessons/tree/error/property-mutation


ссылка на оригинал статьи https://habr.com/ru/post/481824/


Комментарии

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

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