← Назад к вопросам

Как отслеживать изменения в объекте?

2.0 Middle🔥 134 комментариев
#JavaScript Core

Комментарии (4)

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Отслеживание изменений в объекте

В JavaScript есть несколько подходов для отслеживания изменений объектов, каждый со своими преимуществами и недостатками.

1. Object.observe (устарел)

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

2. Proxy - современный подход

Proxy - это встроенный объект JavaScript, позволяющий перехватывать операции с объектом. Это наиболее гибкий и мощный способ:

const createReactiveObject = (target, callback) => {
  return new Proxy(target, {
    set(obj, prop, value) {
      const oldValue = obj[prop];
      if (oldValue === value) return true;
      
      obj[prop] = value;
      callback({
        type: 'set',
        prop,
        oldValue,
        newValue: value,
        timestamp: Date.now()
      });
      return true;
    },
    
    deleteProperty(obj, prop) {
      if (!(prop in obj)) return false;
      
      const value = obj[prop];
      delete obj[prop];
      callback({
        type: 'delete',
        prop,
        value,
        timestamp: Date.now()
      });
      return true;
    }
  });
};

// Использование
const user = createReactiveObject(
  { name: 'John', age: 30 },
  (change) => console.log('Change detected:', change)
);

user.name = 'Jane'; // Change detected: { type: 'set', prop: 'name', ... }
delete user.age;    // Change detected: { type: 'delete', prop: 'age', ... }

3. Глубокое отслеживание с Proxy

Для вложенных объектов нужно применить Proxy рекурсивно:

const createDeepReactive = (target, callback, path = '') => {
  return new Proxy(target, {
    get(obj, prop) {
      const value = obj[prop];
      const fullPath = path ? `${path}.${String(prop)}` : String(prop);
      
      // Если значение - объект, оборачиваем его в Proxy
      if (value !== null && typeof value === 'object') {
        return createDeepReactive(value, callback, fullPath);
      }
      return value;
    },
    
    set(obj, prop, value) {
      const oldValue = obj[prop];
      if (oldValue === value) return true;
      
      obj[prop] = value;
      const fullPath = path ? `${path}.${String(prop)}` : String(prop);
      callback({ path: fullPath, oldValue, newValue: value });
      return true;
    }
  });
};

// Использование
const config = createDeepReactive(
  {
    api: { host: 'localhost', port: 3000 },
    db: { name: 'mydb' }
  },
  (change) => console.log('Config changed:', change)
);

config.api.host = '127.0.0.1'; // Config changed: { path: 'api.host', ... }

4. Getters и Setters

Для простых случаев можно использовать getters и setters:

class User {
  #name = '';
  #watchers = [];
  
  constructor(name) {
    this.#name = name;
  }
  
  get name() {
    return this.#name;
  }
  
  set name(value) {
    const oldValue = this.#name;
    this.#name = value;
    this.#notifyWatchers('name', oldValue, value);
  }
  
  watch(callback) {
    this.#watchers.push(callback);
    return () => {
      this.#watchers = this.#watchers.filter(w => w !== callback);
    };
  }
  
  #notifyWatchers(prop, oldValue, newValue) {
    this.#watchers.forEach(w => w({ prop, oldValue, newValue }));
  }
}

const user = new User('John');
user.watch((change) => console.log('User changed:', change));
user.name = 'Jane'; // User changed: { prop: 'name', oldValue: 'John', newValue: 'Jane' }

5. Event-ориентированный подход с EventEmitter

Для более сложных сценариев используйте паттерн Observer:

class Observable {
  constructor(value) {
    this.value = value;
    this.subscribers = [];
  }
  
  subscribe(callback) {
    this.subscribers.push(callback);
    // Вернуть функцию для отписки
    return () => {
      this.subscribers = this.subscribers.filter(s => s !== callback);
    };
  }
  
  setValue(newValue) {
    const oldValue = this.value;
    this.value = newValue;
    this.subscribers.forEach(callback => {
      callback(newValue, oldValue);
    });
  }
}

const count = new Observable(0);
const unsubscribe = count.subscribe((newVal, oldVal) => {
  console.log(`Changed from ${oldVal} to ${newVal}`);
});

count.setValue(1); // Changed from 0 to 1
count.setValue(2); // Changed from 1 to 2
unsubscribe();     // Отписались
count.setValue(3); // Ничего не выведет

6. В React - использование useState и useEffect

В React для отслеживания изменений используйте встроенные хуки:

import { useState, useEffect } from 'react';

const Component = () => {
  const [user, setUser] = useState({ name: 'John', age: 30 });
  
  // Следить за всеми изменениями объекта
  useEffect(() => {
    console.log('User changed:', user);
  }, [user]);
  
  // Следить только за изменением name
  useEffect(() => {
    console.log('Name changed:', user.name);
  }, [user.name]);
  
  return (
    <button onClick={() => setUser({ ...user, name: 'Jane' })}>
      Change Name
    </button>
  );
};

7. Comparator функции для глубокого сравнения

Для отслеживания изменений вложенных объектов нужно глубокое сравнение:

const deepEqual = (obj1, obj2) => {
  if (obj1 === obj2) return true;
  if (obj1 == null || obj2 == null) return false;
  if (typeof obj1 !== 'object' || typeof obj2 !== 'object') return false;
  
  const keys1 = Object.keys(obj1);
  const keys2 = Object.keys(obj2);
  
  if (keys1.length !== keys2.length) return false;
  
  return keys1.every(key => deepEqual(obj1[key], obj2[key]));
};

const obj1 = { a: 1, b: { c: 2 } };
const obj2 = { a: 1, b: { c: 2 } };
console.log(deepEqual(obj1, obj2)); // true

Рекомендации

  1. Для простых случаев - используйте Getters/Setters или useState в React
  2. Для сложной реактивности - используйте Proxy
  3. Для глубокого отслеживания - комбинируйте Proxy с рекурсией
  4. В React - используйте встроенные хуки (useState, useEffect)
  5. Для производительности - обрабатывайте изменения батчами (debounce, throttle)

Выбор метода зависит от сложности объекта и требований к производительности.