Как стать автором
Обновить

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

Время на прочтение4 мин
Количество просмотров2.2K
Привет! 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

Теги:
Хабы:
Всего голосов 7: ↑3 и ↓4+3
Комментарии6

Публикации

Истории

Работа

Ближайшие события