12

I have a simple ES6 class, like so:

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

I want to make it so that the indexing for Ring objects wraps, so that new Ring(1, 2, 3)[3] returns 1, new Ring(1, 2, 3)[-1] returns 3, and so on. Is this possible in ES6? If so, how would I implement it?

I've read about proxies, which allow a completely customized getter, but I can't figure out how to apply a proxy to a class. I did manage this:

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 is now a Ring object that supports wrapping indices. The problem is that I'd have to define Ring objects like that every time. Is there a way to apply this proxy to the class such that calling new Ring() returns it?

1
  • 1
    Just wrap new Proxy (...) with constructor function and call it with new. Yes, you can't do that without proxy. Commented Oct 1, 2016 at 2:35

3 Answers 3

7

Basically it is

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;
  }
}
Sign up to request clarification or add additional context in comments.

3 Comments

Well... this seems to work perfectly! I didn't realize that the constructor function could explicitly return a value...
Huh... Wow, I never knew you could return a value from a constructor in ES6's classes...
Yes, they are. In most cases that would be unjustified, but this is exactly the case for that.
3

Warning: This is an ugly hack

This is a rather simple approach when you think about it.

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

This defined a function ClassToProxy. The first argument is the class you want to add behavior too, and the second is the handler.


Here's example usage:

const Ring = ClassToProxy(

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

    // Handler
    {
        get: function(target, name) {
            return target.items[name];
        }
    }
)

1 Comment

Hmm... That's pretty close, except it doesn't support new.
0

You basically have two choices:

  • Wrap a Proxy around each instance

    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);
        }
        …
    }
    
  • wrap a Proxy around the prototype of your class

    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];
        }
    });
    

2 Comments

The second method doesn't seem to work for me. I tried using just the Proxy part to extend the Number class, and I managed to get that to work, by changing both instances of Number.prototype to Number.prototype.__proto__.
@ETHproductions Not sure why you'd make Number.prototype a proxy? And you probably should change a .__proto__, as that does introduce another object in the prototype chain.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.