¿Cómo sobrecargaría el operador [] en javascript?

85

Parece que no puedo encontrar la manera de sobrecargar el operador [] en javascript. ¿Alguien lo sabe?

Estaba pensando en las líneas de ...

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

o no estoy mirando las cosas correctas.

slyprid
fuente
3
Las respuestas aquí son incorrectas, las matrices en JS son solo objetos cuyas claves se pueden convertir en valores de uint32 (- 1) y tienen métodos adicionales en su prototipo
Benjamin Gruenbaum
Simplemente convierta su MyClassobjeto en una matriz. Puede copiar las claves y los valores de myArraya su var myObj = new MyClass()objeto.
jpaugh
hey, me gustaría sobrecargar el operador {}, ¿alguna idea?
daniel ensayoag

Respuestas:

81

No puede sobrecargar operadores en JavaScript.

Se propuso para ECMAScript 4 pero se rechazó.

No creo que lo veas pronto.

Peter Bailey
fuente
4
Esto puede ser posible con proxies en algunos navegadores y llegará a todos los navegadores en algún momento. Ver github.com/DavidBruant/ProxyArray
Tristan
Entonces, ¿cómo devuelve jQuery cosas diferentes dependiendo de si usa [] o .eq ()? stackoverflow.com/a/6993901/829305
Rikki
2
Puede hacerlo ahora con un Proxy.
Eyal
aunque puede definir métodos con símbolos siempre que acceda a ellos como una matriz en lugar de con ".". Así es como SmallTalk mapea cosas Object arg1: a arg2: b arg3: ccomo Object["arg1:arg2:arg3:"](a,b,c). Entonces puedes tener myObject["[]"](1024): P
Dmitry
El enlace está muerto :(
Gp2mv3
69

Puede hacer esto con ES6 Proxy (disponible en todos los navegadores modernos)

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

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

Verifique los detalles en MDN .

Joe promedio
fuente
2
¿Cómo usaríamos esto para crear nuestra propia clase con un descriptor de acceso? es decir, quiero usar mi propio constructor, no quiero construir un Proxy.
mpen
1
Esto no es una verdadera sobrecarga. En lugar de llamar a métodos en el propio objeto, ahora está llamando a métodos del proxy.
Pacerier
@Pacerier puede regresar target[name]en el getter, OP solo muestra los ejemplos
Valen
1
[]También funciona con el operador, por cierto:var key = 'world'; console.log(proxy[key]);
Klesun
15

La respuesta simple es que JavaScript permite el acceso a los elementos secundarios de un objeto mediante los corchetes.

Entonces podrías definir tu clase:

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

Luego podrá acceder a los miembros en cualquier instancia de su clase con cualquiera de las sintaxis.

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
Brandon McKinney
fuente
2
Definitivamente no recomendaría esto por razones de rendimiento, pero es la única solución real al problema aquí. Quizás una edición que indique que no es posible haría de esta una gran respuesta.
Milimétrico
4
Esto no es lo que quiere la pregunta. La pregunta es buscar una forma de detectar lo foo['random']que su código no puede hacer.
Pacerier
9

Utilice un proxy. Se mencionó en otra parte de las respuestas, pero creo que este es un mejor ejemplo:

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.
Eyal
fuente
Probablemente valga la pena mencionar que los proxies son una característica de ES6 y, por lo tanto, tienen un soporte de navegador más limitado (y Babel tampoco puede falsificarlos ).
jdehesa
8

Como el operador de paréntesis es en realidad un operador de acceso a la propiedad, puede conectarlo con captadores y definidores. Para IE, tendrá que usar Object.defineProperty () en su lugar. Ejemplo:

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

obj.attr = 123;

Lo mismo para IE8 +:

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

Para IE5-7 solo hay onpropertychangeevento, que funciona para elementos DOM, pero no para otros objetos.

El inconveniente del método es que solo puede enlazar solicitudes a un conjunto predefinido de propiedades, no a una propiedad arbitraria sin un nombre predefinido.

kstep
fuente
¿Podría hacer una demostración de su enfoque en jsfiddle.net ? Supongo que la solución debería funcionar para cualquier clave en la expresión, obj['any_key'] = 123;pero lo que veo en su código, necesito definir setter / getter para cualquier clave (aún no conocida). Eso es imposible.
dma_k
3
más 1 para compensar el menos 1 porque esto no es solo IE.
orbe
¿Podría hacerse esto para una función de clase? Estoy luchando por encontrar la sintaxis por mi cuenta.
Michael Hoffmann
7

Necesita usar Proxy como se explica, pero finalmente se puede integrar en un constructor de clases

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

con este'. Entonces se activarán las funciones set y get (también deleteProperty). Aunque obtienes un objeto Proxy que parece diferente, en su mayor parte funciona preguntarle al comparar (target.constructor === MyClass) es el tipo de clase, etc. [aunque es una función donde target.constructor.name es el nombre de la clase en texto (solo señalando un ejemplo de cosas que funcionan de manera ligeramente diferente)]

Maestro James
fuente
1
Esto funciona bien para sobrecargar [] en una colección Backbone para que los objetos de modelo individuales se devuelvan al usar [] con una transferencia para todas las demás propiedades.
Justkt
5

Así que espera hacer algo como var anything = MyClassInstance [4]; ? Si es así, la respuesta simple es que Javascript no admite actualmente la sobrecarga del operador.

Matt Molnar
fuente
1
Entonces, ¿cómo funciona jQuery? Puede llamar a un método en el objeto jQuery como $ ('. Foo'). Html () u obtener el primer elemento dom coincidente como $ ('. Foo') [0]
kagronick
1
jQuery es una función, estás pasando un parámetro a la función $. De ahí los corchetes (), no []
James Westgate
5

una forma engañosa de hacer esto es extendiendo el lenguaje en sí.

paso 1

definir una convención de indexación personalizada, llamémosla, "[]".

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

paso 2

definir una nueva implementación de evaluación. (no hagas esto de esta manera, pero es una prueba de concepto).

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

lo anterior no funcionará para índices más complejos, pero puede ser con un análisis más sólido.

alternativa:

en lugar de recurrir a crear su propio lenguaje de superconjunto, puede compilar su notación en el lenguaje existente y luego evaluarlo. Esto reduce la sobrecarga de análisis a nativo después de la primera vez que lo usa.

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));
Dmitry
fuente
1
Estás torcido +1
Jus
absolutamente asqueroso. me encanta.
SliceThePi
La astucia suele ser de una forma u otra. No significa que no sea un ejercicio que valga la pena y que pueda dar sus frutos con suficientes recursos. Las personas más perezosas son aquellas que escriben sus propios compiladores y traductores solo para poder trabajar en entornos más familiares, incluso si no están disponibles. Dicho esto, sería menos repugnante si alguien con más experiencia en el área lo escribiera con menos prisa. Todas las soluciones no triviales son repugnantes de una forma u otra, nuestro trabajo es conocer las compensaciones y enfrentar las consecuencias.
Dmitry
3

Podemos obtener proxy | establecer métodos directamente. Inspirado por esto .

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]
Jaber
fuente