> Как отслеживать изменения поля объекта без изменения исходного кода? (JavaScript)

Уровень: senior · Роль: frontend · Категория: Технические вопросы

Компании: Дом.рф

Стек: JavaScript

> Пример ответа

Короткий ответ

Для отслеживания изменений поля объекта без изменения исходного кода используются Proxy (перехват операций set/get) или Object.defineProperty (переопределение аксессоров). Proxy предпочтительнее, так как не требует модификации самого объекта и поддерживает динамические свойства. Альтернатива - патчинг методов через декораторы или middleware в state-менеджерах (например, MobX observable).

Подробное объяснение

Основная задача - наблюдение за мутациями объекта без внесения изменений в его исходный код. Это достигается через:

  1. Proxy - создаёт обёртку над объектом, перехватывая операции (set, get, deleteProperty). Позволяет отслеживать любые изменения, включая добавление новых полей. Не требует изменения оригинального объекта.

  2. Object.defineProperty - переопределяет конкретные свойства объекта, заменяя их на getter/setter. Требует заранее знать имена полей, не работает с новыми свойствами.

  3. Глубокое проксирование - для вложенных объектов нужна рекурсивная обёртка, так как Proxy не перехватывает изменения внутри вложенных структур по умолчанию.

  4. Патчинг прототипов - изменение методов класса (например, через декораторы TypeScript) для добавления логики отслеживания.

  5. Внешние библиотеки - Vue (reactive), MobX (observable), immer (produce) реализуют это через Proxy под капотом.

Ключевой trade-off: Proxy не поддерживается в IE11, Object.defineProperty не ловит новые свойства. Для production часто используют комбинацию подходов.

На практике

В реальных проектах чаще всего применяют готовые решения:

  • Vue 3 - reactive() использует Proxy для реактивности.
  • MobX - observable() оборачивает объект в Proxy с автоматическим отслеживанием.
  • Immer - produce() создаёт черновик через Proxy, фиксируя изменения.

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

Для отслеживания без изменения исходного кода в legacy-коде часто используют декораторы или middleware (например, в Redux - middleware для логирования экшенов).

Пример кода

JAVASCRIPT
// Базовый пример с Proxy
function createObservable(target, onChange) {
const handler = {
set(obj, prop, value) {
const oldValue = obj[prop];
obj[prop] = value;
onChange(prop, oldValue, value);
return true;
},
get(obj, prop) {
if (typeof obj[prop] === 'object' && obj[prop] !== null) {
return createObservable(obj[prop], onChange); // рекурсия для вложенных
}
return obj[prop];
}
};
return new Proxy(target, handler);
}
// Использование
const user = { name: 'Alice', address: { city: 'NY' } };
const observed = createObservable(user, (prop, oldVal, newVal) => {
console.log(`Изменено ${prop}: ${oldVal} -> ${newVal}`);
});
observed.name = 'Bob'; // Изменено name: Alice -> Bob
observed.address.city = 'LA'; // Изменено city: NY -> LA

Как отвечать на собеседовании

  1. Начни с прямого ответа: назови Proxy как основной инструмент.
  2. Объясни разницу между Proxy и Object.defineProperty - это покажет понимание trade-off.
  3. Упомяни практические сценарии: отладка, реактивность UI, синхронизация.
  4. Добавь про производительность и ограничения (IE11, глубокое проксирование).
  5. Если спросят про вложенные объекты - покажи рекурсивное проксирование.
  6. Для senior-уровня стоит упомянуть WeakMap для кэширования прокси и предотвращения утечек памяти.

Что проверяет интервьюер

  • Понимание метапрограммирования в JavaScript (Proxy, Reflect).
  • Умение выбирать инструмент под задачу (Proxy vs defineProperty).
  • Знание практических ограничений (совместимость, производительность).
  • Способность реализовать глубокое отслеживание без багов (рекурсия, кэширование).
  • Понимание, как это используется в популярных библиотеках (Vue, MobX).

Типичные ошибки

  • Предложение использовать Object.defineProperty без учёта новых свойств.
  • Забывают про рекурсивное проксирование для вложенных объектов.
  • Не учитывают, что Proxy создаёт новый объект - оригинал остаётся неизменным.
  • Пытаются отслеживать изменения через setInterval или polling.
  • Не знают про handler.deleteProperty для отслеживания удаления полей.
  • Путают Proxy с декораторами классов (это разные механизмы).

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью