110

I can't seem to find the way to overload the [] operator in javascript. Anyone out there know?

I was thinking on the lines of ...

MyClass.operator.lookup(index)
{
    return myArray[index];
}

or am I not looking at the right things.

4
  • 3
    The answers here are wrong, Arrays in JS are just objects whose keys are coercable to uint32 ( - 1) values and have extra methods on their prototype Commented Aug 20, 2013 at 10:05
  • Just make your MyClass object an array. You can copy the keys and values from myArray to your var myObj = new MyClass() object. Commented Aug 21, 2017 at 15:23
  • hey, i'd like overload the {} operator, any idea? Commented Jun 17, 2020 at 20:44
  • @danielassayag, {} is not an operator. Commented Mar 26 at 16:55

11 Answers 11

113

You can do this with ES6 Proxy (available in all modern browsers)

var handler = {
    get: function(target, name) {
        return "Hello, " + name;
    }
};
var proxy = new Proxy({}, handler);

console.log(proxy.world); // output: Hello, world
console.log(proxy[123]); // output: Hello, 123

Check details on MDN.

Sign up to request clarification or add additional context in comments.

8 Comments

How would we use this to create our own class with an index accessor? i.e. I want to use my own constructor, I don't want to construct a Proxy.
This isn't true overloading. Instead of calling methods on the object itself, you are now calling methods of the proxy.
@Pacerier you can return target[name] in the getter, OP is just showing the examples
Works with [] operator as well, btw: var key = 'world'; console.log(proxy[key]);
Note that numbers are not special. If you check typeof name for in proxy[123] or proxy[new Date()] you'll see they are all coerced to strings.
|
87

This answer is outdated as of ES6. See average Joe's answer for an answer using new features in ES6. However, this answer is still true for browsers that lack ES6 support.

You can't overload operators in JavaScript.

It was proposed for ECMAScript 4 but rejected.

I don't think you'll see it anytime soon.

7 Comments

This may be doable with proxies in some browsers already, and will be coming to all browsers at some point. See github.com/DavidBruant/ProxyArray
Then how does jQuery return different things dependent on whether you use [] or .eq()? stackoverflow.com/a/6993901/829305
You can do it now with a Proxy.
although you can define methods with symbols in them as long as you access them as an array rather than with ".". That's how SmallTalk maps things like Object arg1: a arg2: b arg3: c as Object["arg1:arg2:arg3:"](a,b,c). So you can have myObject["[]"](1024) :P
The link is dead :(
|
16

We can proxy get | set methods directly. Inspired by this.

class Foo {
    constructor(v) {
        this.data = v
        return new Proxy(this, {
            get: (obj, key) => {
                if (typeof(key) === 'string' && (Number.isInteger(Number(key)))) // key is an index
                    return obj.data[key]
                else 
                    return obj[key]
            },
            set: (obj, key, value) => {
                if (typeof(key) === 'string' && (Number.isInteger(Number(key)))) // key is an index
                    return obj.data[key] = value
                else 
                    return obj[key] = value
            }
        })
    }
}

var foo = new Foo([])

foo.data = [0, 0, 0]
foo[0] = 1
console.log(foo[0]) // 1
console.log(foo.data) // [1, 0, 0]

2 Comments

Other than I don't understand why you're requiring the key to be a string, this seems like the only answer here that actually accomplishes what the OP is after.
Oops nevermind my ignorant comment about the string key. But does this kill performance vs. a true array?
15

The simple answer is that JavaScript allows access to children of an Object via the square brackets.

So you could define your class:

MyClass = function(){
    // Set some defaults that belong to the class via dot syntax or array syntax.
    this.some_property = 'my value is a string';
    this['another_property'] = 'i am also a string';
    this[0] = 1;
};

You will then be able to access the members on any instances of your class with either syntax.

foo = new MyClass();
foo.some_property;  // Returns 'my value is a string'
foo['some_property'];  // Returns 'my value is a string'
foo.another_property;  // Returns  'i am also a string'
foo['another_property'];  // Also returns 'i am also a string'
foo.0;  // Syntax Error
foo[0];  // Returns 1
foo['0'];  // Returns 1

2 Comments

i would definitely not recommend this for performance reasons, but it's the only actual solution to the problem here. Perhaps an edit stating that it's not possible would make this a great answer.
This isn't what the question wants. The question is asking for a way to catch foo['random'] which your code is not able to do.
15

Use a proxy. It was mentioned elsewhere in the answers but I think that this is a better example:

var handler = {
    get: function(target, name) {
        if (name in target) {
            return target[name];
        }
        if (name == 'length') {
            return Infinity;
        }
        return name * name;
    }
};
var p = new Proxy({}, handler);

p[4]; //returns 16, which is the square of 4.

1 Comment

Probably worth mentioning that proxies are an ES6 feature and therefore have a more limited browser support (and Babel cannot fake them either).
9

As brackets operator is actually property access operator, you can hook on it with getters and setters. For IE you will have to use Object.defineProperty() instead. Example:

var obj = {
    get attr() { alert("Getter called!"); return 1; },
    set attr(value) { alert("Setter called!"); return value; }
};

obj.attr = 123;

The same for IE8+:

Object.defineProperty("attr", {
    get: function() { alert("Getter called!"); return 1; },
    set: function(value) { alert("Setter called!"); return value; }
});

For IE5-7 there's onpropertychange event only, which works for DOM elements, but not for other objects.

The drawback of the method is you can only hook on requests to predefined set of properties, not on arbitrary property without any predefined name.

4 Comments

Could you please demo your approach on jsfiddle.net ? I assume that solution should work for any key in expression obj['any_key'] = 123; but what I see in your code I need to define setter/getter for any (not yet known) key. That is impossible.
plus 1 to offset the minus 1 because this is not IE only.
Could this be done for a class function? I'm struggling to find the syntax for that on my own.
It seems this can actually be called on an arbitrary property: var a = new Array(2); function trap_indexing(obj,index) { Object.defineProperty(obj,index,{ get() { console.log("getting"); return this['_shadow'+index]; }, set(p) { console.log("setting"); this['_shadow'+index] = p; } }); } trap_indexing(a,0); trap_indexing(a,1); trap_indexing(a,2); a[0] = 'barf'; console.log(a[0]); a[1] = 'cat'; console.log(a[1]);
7

one sneaky way to do this is by extending the language itself.

step 1

define a custom indexing convention, let's call it, "[]".

var MyClass = function MyClass(n) {
    this.myArray = Array.from(Array(n).keys()).map(a => 0);
};
Object.defineProperty(MyClass.prototype, "[]", {
    value: function(index) {
        return this.myArray[index];
    }
});

...

var foo = new MyClass(1024);
console.log(foo["[]"](0));

step 2

define a new eval implementation. (don't do this this way, but it's a proof of concept).

var MyClass = function MyClass(length, defaultValue) {
    this.myArray = Array.from(Array(length).keys()).map(a => defaultValue);
};
Object.defineProperty(MyClass.prototype, "[]", {
    value: function(index) {
        return this.myArray[index];
    }
});

var foo = new MyClass(1024, 1337);
console.log(foo["[]"](0));

var mini_eval = function(program) {
    var esprima = require("esprima");
    var tokens = esprima.tokenize(program);
    
    if (tokens.length == 4) {    
        var types = tokens.map(a => a.type);
        var values = tokens.map(a => a.value);
        if (types.join(';').match(/Identifier;Punctuator;[^;]+;Punctuator/)) {
            if (values[1] == '[' && values[3] == ']') {
                var target = eval(values[0]);
                var i = eval(values[2]);
                // higher priority than []                
                if (target.hasOwnProperty('[]')) {
                    return target['[]'](i);
                } else {
                    return target[i];
                }
                return eval(values[0])();
            } else {
                return undefined;
            }
        } else {
            return undefined;
        }
    } else {
        return undefined;
    }    
};

mini_eval("foo[33]");

the above won't work for more complex indexes but it can be with stronger parsing.

alternative:

instead of resorting to creating your own superset language, you can instead compile your notation to the existing language, then eval it. This reduces the parsing overhead to native after the first time you use it.

var compile = function(program) {
    var esprima = require("esprima");
    var tokens = esprima.tokenize(program);
    
    if (tokens.length == 4) {    
        var types = tokens.map(a => a.type);
        var values = tokens.map(a => a.value);
        if (types.join(';').match(/Identifier;Punctuator;[^;]+;Punctuator/)) {
            if (values[1] == '[' && values[3] == ']') {
                var target = values[0];
                var i = values[2];
                // higher priority than []                
                return `
                    (${target}['[]']) 
                        ? ${target}['[]'](${i}) 
                        : ${target}[${i}]`
            } else {
                return 'undefined';
            }
        } else {
            return 'undefined';
        }
    } else {
        return 'undefined';
    }    
};

var result = compile("foo[0]");
console.log(result);
console.log(eval(result));

1 Comment

Cleverness usually is in one way or another. Doesn't mean it's not a worthwhile exercise that can pay off with enough resources. The laziest people are those that write their own compilers and translators just so they can work in more familiar environments, even if they're not available. That said, it would be less disgusting if it was written in less of a hurry by somebody with more experience in the area. All nontrivial solutions are disgusting in one way or another, our job is knowing the tradeoffs and coping with consequences.
7

You need to use Proxy as explained, but it can ultimately be integrated into a class constructor

return new Proxy(this, {
    set: function( target, name, value ) {
...}};

with 'this'. Then the set and get (also deleteProperty) functions will fire. Although you get a Proxy object which seems different it for the most part works to ask the compare ( target.constructor === MyClass ) it's class type etc. [even though it's a function where target.constructor.name is the class name in text (just noting an example of things that work slightly different.)]

1 Comment

This works well for overloading the [] on a Backbone collection so that the individual model objects are returned on using [] with a pass-through for all other properties.
4

So you're hoping to do something like var whatever = MyClassInstance[4]; ? If so, simple answer is that Javascript does not currently support operator overloading.

2 Comments

So how does jQuery work. You could call a method on the jQuery object like $('.foo').html() or get the first matching dom element like $('.foo')[0]
jQuery is a function, you are passing a parameter to the $ function. Hence the () brackets, not []
1

Have a look at Symbol.iterator. You can implement a user-defined @@iterator method to make any object iterable.

The well-known Symbol.iterator symbol specifies the default iterator for an object. Used by for...of.

Example:

class MyClass {

  constructor () {
    this._array = [data]
  }

  *[Symbol.iterator] () {
    for (let i=0, n=this._array.length; i<n; i++) {
      yield this._array[i]
    }
  }
}

const c = new MyClass()

for (const element of [...c]) {
  // do something with element
}

1 Comment

So this lets you iterate, but can you access arbitrary elements by index?
1

class and proxy can be combined:

class Overload {
    #data = {};
    constructor () {
        return new Proxy(this, this);
    }
    get (_, p) {
        return this.#data[p];
    }
}

remember that proxy handles all prop access.

Comments

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.