Come sovraccaricheresti l'operatore [] in javascript


86

Non riesco a trovare il modo per sovraccaricare l'operatore [] in javascript. Qualcuno là fuori lo sa?

Stavo pensando sulla falsariga di ...

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

o non sto guardando le cose giuste.


3
Le risposte qui sono sbagliate, gli array in JS sono solo oggetti le cui chiavi possono essere convertite in valori uint32 (-1) e hanno metodi extra sul loro prototipo
Benjamin Gruenbaum

Rendi il tuo MyClassoggetto un array. Puoi copiare le chiavi e i valori da myArraynel tuo var myObj = new MyClass()oggetto.
jpaugh

hey, vorrei sovraccaricare l'operatore {}, qualche idea?
daniel assayag

Risposte:


81

Non puoi sovraccaricare gli operatori in JavaScript.

È stato proposto per ECMAScript 4 ma rifiutato.

Non credo che lo vedrai presto.


4
Ciò potrebbe essere già possibile con i proxy in alcuni browser e ad un certo punto arriverà su tutti i browser. Vedi github.com/DavidBruant/ProxyArray
Tristan

Allora come fa jQuery a restituire cose diverse a seconda che tu usi [] o .eq ()? stackoverflow.com/a/6993901/829305
Rikki

2
Ora puoi farlo con un proxy.
Eyal

sebbene tu possa definire metodi con simboli al loro interno purché tu acceda ad essi come un array invece che con ".". È così che associa le cose come SmallTalk Object arg1: a arg2: b arg3: ccome Object["arg1:arg2:arg3:"](a,b,c). Quindi puoi avere myObject["[]"](1024): P
Dmitry

Il collegamento è morto :(
Gp2mv3

71

Puoi farlo con ES6 Proxy (disponibile in tutti i browser moderni)

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

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

Controlla i dettagli su MDN .


2
Come lo useremmo per creare la nostra classe con una funzione di accesso di indice? cioè voglio usare il mio costruttore, non voglio costruire un proxy.
mpen

1
Questo non è vero sovraccarico. Invece di chiamare metodi sull'oggetto stesso, ora stai chiamando metodi del proxy.
Pacerier

@Pacerier puoi tornare target[name]nel getter, OP mostra solo gli esempi
Valen

1
Funziona anche con l' []operatore, tra l'altro:var key = 'world'; console.log(proxy[key]);
Klesun

15

La semplice risposta è che JavaScript consente l'accesso ai figli di un oggetto tramite le parentesi quadre.

Quindi potresti definire la tua classe:

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

Sarai quindi in grado di accedere ai membri su qualsiasi istanza della tua classe con entrambe le sintassi.

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
Non lo consiglierei sicuramente per motivi di prestazioni, ma è l'unica vera soluzione al problema qui. Forse una modifica che afferma che non è possibile renderebbe questa un'ottima risposta.
Milimetrico

4
Questo non è ciò che vuole la domanda. La domanda è chiedere un modo per individuare ciò foo['random']che il tuo codice non è in grado di fare.
Pacerier

9

Usa un proxy. È stato menzionato altrove nelle risposte ma penso che questo sia un esempio migliore:

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.

Probabilmente vale la pena ricordare che i proxy sono una funzionalità di ES6 e quindi hanno un supporto browser più limitato (e Babel non può nemmeno falsificarli ).
jdehesa

8

Poiché l'operatore delle parentesi è in realtà un operatore di accesso alla proprietà, puoi agganciarlo con getter e setter. Per IE dovrai invece usare Object.defineProperty (). Esempio:

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

obj.attr = 123;

Lo stesso per IE8 +:

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

Per IE5-7 c'è onpropertychangesolo l'evento, che funziona per gli elementi DOM, ma non per altri oggetti.

Lo svantaggio del metodo è che puoi solo agganciare le richieste a un insieme predefinito di proprietà, non su proprietà arbitrarie senza alcun nome predefinito.


Potresti per favore dimostrare il tuo approccio su jsfiddle.net ? Presumo che la soluzione dovrebbe funzionare per qualsiasi chiave nell'espressione, obj['any_key'] = 123;ma quello che vedo nel tuo codice devo definire setter / getter per qualsiasi chiave (non ancora nota). Questo è impossibile.
dma_k

3
più 1 per compensare il meno 1 perché non è solo IE.
orb

Potrebbe essere fatto per una funzione di classe? Sto lottando per trovare la sintassi per quello da solo.
Michael Hoffmann

7

È necessario utilizzare Proxy come spiegato, ma alla fine può essere integrato in un costruttore di classi

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

con questo'. Quindi verranno attivate le funzioni set e get (anche deleteProperty). Anche se ottieni un oggetto Proxy che sembra diverso, per la maggior parte funziona per chiedere il confronto (target.constructor === MyClass) è il tipo di classe ecc. [Anche se è una funzione in cui target.constructor.name è il nome della classe in testo (solo annotando un esempio di cose che funzionano in modo leggermente diverso.)]


1
Funziona bene per sovraccaricare [] su una raccolta Backbone in modo che i singoli oggetti del modello vengano restituiti utilizzando [] con un pass-through per tutte le altre proprietà.
solo il

5

Quindi speri di fare qualcosa come var qualunque = MyClassInstance [4]; ? In tal caso, la semplice risposta è che Javascript attualmente non supporta il sovraccarico dell'operatore.


1
Allora come funziona jQuery. È possibile chiamare un metodo sull'oggetto jQuery come $ ('. Foo'). Html () o ottenere il primo elemento dom corrispondente come $ ('. Foo') [0]
kagronick

1
jQuery è una funzione, stai passando un parametro alla funzione $. Da qui le parentesi (), non []
James Westgate

5

un modo subdolo per farlo è estendere la lingua stessa.

passo 1

definire una convenzione di indicizzazione personalizzata, chiamiamola "[]".

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

passo 2

definire una nuova implementazione della valutazione. (non farlo in questo modo, ma è una prova di concetto).

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

quanto sopra non funzionerà per indici più complessi ma può essere con un'analisi più forte.

alternativa:

invece di ricorrere alla creazione della tua lingua superset, puoi invece compilare la tua notazione nella lingua esistente, quindi valutarla. Ciò riduce l'overhead di analisi a nativo dopo la prima volta che lo usi.

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

3
Sei storto +1
Jus

1
assolutamente disgustoso. Lo adoro.
SliceThePi

L'intelligenza di solito è in un modo o nell'altro. Non significa che non sia un esercizio utile che può essere ripagato con risorse sufficienti. Le persone più pigre sono quelle che scrivono i propri compilatori e traduttori solo per poter lavorare in ambienti più familiari, anche se non sono disponibili. Detto questo, sarebbe meno disgustoso se fosse scritto con meno fretta da qualcuno con più esperienza nella zona. Tutte le soluzioni non banali sono disgustose in un modo o nell'altro, il nostro lavoro è conoscere i compromessi e far fronte alle conseguenze.
Dmitry

3

Possiamo ottenere tramite proxy | impostare direttamente i metodi. Ispirato da questo .

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]
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.