Пользовательский массив-подобный геттер в JavaScript

У меня есть простой класс ES6, например:

class Ring extends Array {
    insert (item, index) {
        this.splice(index, 0, item);
        return this;
    }
}

Я хочу сделать так, чтобы индексация объектов Ring переносилась, чтобы new Ring(1, 2, 3)[3] возвращала 1, new Ring(1, 2, 3)[-1] возвращала 3 и так далее. Возможно ли это в ES6? Если да, то как бы я это реализовал?

Я читал о прокси, которые позволяют полностью настроить геттер, но я не могу понять, как применить прокси к классу. Я справился с этим:

var myRing = new Proxy (Ring.prototype, {
    get: function (target, name) {
        var len = target.length;
        if (/^-?\d+$/.test(name))
            return target[(name % len + len) % len];
        return target[name];
    }
});

myRing теперь является кольцевым объектом, поддерживающим перенос индексов. Проблема в том, что мне пришлось бы каждый раз определять объекты Ring подобным образом. Есть ли способ применить этот прокси к классу, чтобы вызов new Ring() возвращал его?


person ETHproductions    schedule 01.10.2016    source источник
comment
Просто оберните new Proxy (...) функцией-конструктором и вызовите ее с помощью new. Да, вы не можете сделать это без прокси.   -  person Estus Flask    schedule 01.10.2016


Ответы (3)


В основном это

class ProxyRing extends Array {
  constructor(...args) {
    super(...args)

    return new Proxy(this, {
      get: function (target, name) {
          var len = target.length;
          if (typeof name === 'string' && /^-?\d+$/.test(name))
              return target[(name % len + len) % len];
          return target[name];
      }
    });
  }

  insert (item, index) {
      this.splice(index, 0, item);
      return this;
  }
}
person Estus Flask    schedule 01.10.2016
comment
Что ж... кажется, это работает отлично! Я не понимал, что функция-конструктор может явно возвращать значение... - person ETHproductions; 01.10.2016
comment
Хм... Ничего себе, я никогда не знал, что вы можете вернуть значение из конструктора в классах ES6... - person Downgoat; 01.10.2016
comment
Да, да. В большинстве случаев это было бы неоправданно, но это именно тот случай. - person Estus Flask; 01.10.2016

Внимание! Это уродливый взлом

Это довольно простой подход, если подумать.

function ClassToProxy(_class, handler) {
    return (...args) => new Proxy(new _class(...args), handler);
}

Это определило функцию ClassToProxy. Первый аргумент — это класс, к которому вы хотите добавить поведение, а второй — обработчик.


Вот пример использования:

const Ring = ClassToProxy(

    // Class
    class Ring {
        constructor(...items) {
            this.items = items;
        }
    },

    // Handler
    {
        get: function(target, name) {
            return target.items[name];
        }
    }
)
person Downgoat    schedule 01.10.2016
comment
Хм... Это довольно близко, за исключением того, что он не поддерживает new. - person ETHproductions; 01.10.2016

В основном у вас есть два варианта:

  • Оберните Proxy вокруг каждого экземпляра

    const handler = {
        get(target, name) {
            var len = target.length;
            if (typeof name === 'string' && /^-?\d+$/.test(name))
                return target[(name % len + len) % len];
            return target[name];
        }
    };
    class Ring extends Array {
        constructor() {
            super()
            return new Proxy(this, handler);
        }
        …
    }
    
  • оберните Proxy вокруг прототипа вашего класса

    class Ring extends Array {
        constructor() {
            super()
        }
        …
    }
    Ring.prototype = new Proxy(Ring.prototype, {
        get(target, name, receiver) {
            var len = target.length;
            if (typeof name === 'string' && /^-?\d+$/.test(name)) {
                if (+name < 0)
                    return receiver[(name % len) + len];
                if (+name > len-1)
                    return receiver[name % len];
            }
            return target[name];
        }
    });
    
person Bergi    schedule 01.10.2016
comment
Второй способ мне кажется не подходит. Я попытался использовать только часть Proxy для расширения класса Number, и мне удалось заставить это работать, изменив оба экземпляра Number.prototype на Number.prototype.__proto__. - person ETHproductions; 04.10.2016
comment
@ETHproductions Не знаете, зачем делать Number.prototype прокси? И вам, вероятно, следует изменить .__proto__, так как это добавляет еще один объект в цепочку прототипов. - person Bergi; 04.10.2016