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

Индексаторы (Indexers) в JavaScript

Время на прочтение3 мин
Количество просмотров6.5K

В некоторых современных объектно-ориентированных языках есть понятие идексаторов – свойств, позволяющих работать с экземпляром класса как с массивом, используя [] нотацию. В этой статье я хочу продемонстрировать как это сделать на современном JavaScript.

Приведу пример на C#:

class Colors {     
  private Dictionary<string, Color> inner = new Dictionary<string, Color>();      
  // реализуем индексатор     
  public Color this[string name]     {         
    get => inner[name];         
    set => inner[name] = value;     
  } 
}

class Program {     
  static void Main(string[] args)     {         
    Colors c = new Colors();         
    // обращаемся к свойству используя [] нотацию         
    c["red"] = Color.Red;         
    c["yellow"] = Color.Yellow;     
  } 
}

К сожалению, пока такой возможности современный ES6 напрямую не предлагает. Тем не менее, в ES6 есть механизмы позволяющие добиться подобного поведения, хоть это будет выглядеть и работать не так изящно, как в том же C#.

В ES6 есть особый класс Proxy, позволяющий перехватывать обращения к базовому классу. Таким образом, появляется возможность особым образом обрабатывать обращения к полям класса.

Давайте повторим наш C# пример на JS без каких-либо хитростей.

class Colors {
    #colors = new Map();
  
    getColor(key) {
        // тут может быть сложная логика
        return this.#colors.get(key);
    }

    setColor(key, value) {
        // тут может быть сложная логика
        this.#colors.set(key, value);
    }

    hasColor(key) {
        return this.#colors.has(key);
    }
}

const colors = new Colors();
colors.setColor('red', ...);
colors.setColor('yellow', ...);
colors.hasColor(‘red’); => true

Чтобы добавить цвет в набор мы вместо нотации [] используем метод setColor(). Исправим это дело добавив магию Proxy. Для этого объявим в классе Colors конструктор и будем вместо экземпляра Colors возвращать его Proxy обертку с обработчиками get и set.

    constructor() {
        return new Proxy(this, {
            // перекрывает получение Colors.name
            get(target, name) {
                if (name in target) {
                    const result = target[name];
                    return typeof result === 'function' ? result.bind(target) : result;
                }
                return target.getColor(name);
            },
            // перекрывает установку Colors.name
            set(target, name, value) {
                if (name in target)
                    target[name] = value;
                else
                    target.setColor(name, value);
                return true;
            }
        });
    }

Тут необходимо дополнительно пояснить некоторый код обработчика get:

if (name in target) {…} 

Этот фрагмент нужен чтобы дать доступ к свойствам и методам класса (например, hasColor), и только если такого свойства или метода в классе нет, то вызов пойдет в getColor().

return typeof result === 'function' ? result.bind(target) : result;

Тут функции биндятся к target (т.е. к экземпляру Color), так как иначе внутри они получат this = Proxy.

После создания такого конструктора нам становится доступен требуемый синтаксис:

const colors = new Colors();

colors['red'] = …;
colors['yellow'] = …;

colors.has('red'); // true
console.log(colors['yellow']);

Несмотря на то, что мы добились требуемого результата, код на JS выглядит скорее как костыль и использовать его я бы рекомендовал только если вы переносите существующий код с другого языка (C#, Delphi), где такие свойства активно использовались.

В свою очередь, мне было бы интересно узнать, есть ли еще способы реализовать [] нотацию для вызова геттера/сеттера без использования Proxy.

Теги:
Хабы:
Всего голосов 3: ↑2 и ↓1+1
Комментарии17

Публикации

Истории

Работа

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