Wie würden Sie den Operator [] in Javascript überladen?

85

Ich kann anscheinend nicht den Weg finden, den Operator [] in Javascript zu überladen. Weiß da draußen jemand Bescheid?

Ich dachte an ...

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

oder schaue ich nicht auf die richtigen Dinge.

Slyprid
quelle
3
Die Antworten hier sind falsch. Arrays in JS sind nur Objekte, deren Schlüssel auf uint32 (- 1) -Werte erzwungen werden können, und haben zusätzliche Methoden für ihren Prototyp
Benjamin Gruenbaum
Machen Sie Ihr MyClassObjekt einfach zu einem Array. Sie können die Schlüssel und Werte von myArrayin Ihr var myObj = new MyClass()Objekt kopieren .
Jpaugh
Hey, ich möchte den {} Operator überladen, eine Idee?
Daniel Assayag

Antworten:

81

Sie können Operatoren in JavaScript nicht überladen.

Es wurde für ECMAScript 4 vorgeschlagen, aber abgelehnt.

Ich glaube nicht, dass du es bald sehen wirst.

Peter Bailey
quelle
4
Dies ist möglicherweise bereits mit Proxys in einigen Browsern möglich und wird irgendwann für alle Browser verfügbar sein. Siehe github.com/DavidBruant/ProxyArray
Tristan
Wie gibt jQuery dann verschiedene Dinge zurück, je nachdem, ob Sie [] oder .eq () verwenden? stackoverflow.com/a/6993901/829305
Rikki
2
Sie können es jetzt mit einem Proxy tun.
Eyal
Sie können jedoch Methoden mit Symbolen definieren, solange Sie als Array und nicht mit "." auf sie zugreifen. So bildet SmallTalk Dinge wie Object arg1: a arg2: b arg3: cab Object["arg1:arg2:arg3:"](a,b,c). So können Sie haben myObject["[]"](1024): P
Dmitry
Der Link ist tot :(
Gp2mv3
69

Sie können dies mit ES6 Proxy tun (in allen modernen Browsern verfügbar).

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

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

Überprüfen Sie die Details zu MDN .

Durchschnittstyp
quelle
2
Wie würden wir dies verwenden, um unsere eigene Klasse mit einem Index-Accessor zu erstellen? dh ich möchte meinen eigenen Konstruktor verwenden, ich möchte keinen Proxy erstellen.
Mpen
1
Dies ist keine echte Überladung. Anstatt Methoden für das Objekt selbst aufzurufen, rufen Sie jetzt Methoden des Proxys auf.
Pacerier
@ Pacerier können Sie target[name]im Getter zurückkehren, OP zeigt nur die Beispiele
Valen
1
Funktioniert auch mit dem []Bediener, übrigens:var key = 'world'; console.log(proxy[key]);
Klesun
15

Die einfache Antwort lautet, dass JavaScript den Zugriff auf untergeordnete Elemente eines Objekts über die eckigen Klammern ermöglicht.

So können Sie Ihre Klasse definieren:

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

Sie können dann mit jeder Syntax auf die Mitglieder in allen Instanzen Ihrer Klasse zugreifen.

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
quelle
2
Ich würde dies aus Leistungsgründen definitiv nicht empfehlen, aber es ist die einzige tatsächliche Lösung für das Problem hier. Vielleicht würde eine Änderung, die besagt, dass dies nicht möglich ist, dies zu einer großartigen Antwort machen.
Milimetric
4
Dies ist nicht , was die Frage will. Die Frage lautet, wie foo['random']Sie feststellen können, was Ihr Code nicht kann.
Pacerier
10

Verwenden Sie einen Proxy. Es wurde an anderer Stelle in den Antworten erwähnt, aber ich denke, dass dies ein besseres Beispiel ist:

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
quelle
Erwähnenswert ist wahrscheinlich, dass Proxys eine ES6-Funktion sind und daher eine eingeschränktere Browserunterstützung haben (und Babel kann sie auch nicht fälschen ).
Jdehesa
8

Da der Operator für Klammern tatsächlich der Operator für den Zugriff auf Eigenschaften ist, können Sie ihn mit Getter und Setter einbinden. Für IE müssen Sie stattdessen Object.defineProperty () verwenden. Beispiel:

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

obj.attr = 123;

Gleiches gilt für IE8 +:

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

Für IE5-7 gibt es onpropertychangenur Ereignisse, die für DOM-Elemente funktionieren, nicht jedoch für andere Objekte.

Der Nachteil der Methode besteht darin, dass Sie nur Anforderungen an vordefinierte Eigenschaften festlegen können, nicht an beliebige Eigenschaften ohne vordefinierten Namen.

kstep
quelle
Könnten Sie bitte Ihren Ansatz auf jsfiddle.net demonstrieren ? Ich gehe davon aus, dass die Lösung für jeden Schlüssel im Ausdruck funktionieren sollte, obj['any_key'] = 123;aber was ich in Ihrem Code sehe, muss ich für jeden (noch nicht bekannten) Schlüssel Setter / Getter definieren. Das ist unmöglich.
dma_k
3
plus 1, um die minus 1 auszugleichen, da dies nicht nur IE ist.
Kugel
Könnte dies für eine Klassenfunktion durchgeführt werden? Ich habe Mühe, die Syntax dafür selbst zu finden.
Michael Hoffmann
7

Sie müssen Proxy verwenden , wie erklärt, aber es kann schließlich in eine Klasse integriert werden Konstruktor

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

mit diesem'. Dann werden die Funktionen set und get (auch deleteProperty) ausgelöst. Obwohl Sie ein Proxy-Objekt erhalten, das anders aussieht, fragt es zum größten Teil nach dem Vergleichstyp (target.constructor === MyClass) nach dem Klassentyp usw. [obwohl es sich um eine Funktion handelt, bei der target.constructor.name der Klassenname ist Text (nur ein Beispiel für Dinge, die etwas anders funktionieren.)]

Meister James
quelle
1
Dies funktioniert gut zum Überladen von [] in einer Backbone-Auflistung, sodass die einzelnen Modellobjekte bei Verwendung von [] mit einem Durchgang für alle anderen Eigenschaften zurückgegeben werden.
Justkt
5

Sie hoffen also, so etwas wie var Whatever = MyClassInstance [4] zu tun; ? Wenn ja, ist die einfache Antwort, dass Javascript derzeit das Überladen von Operatoren nicht unterstützt.

Matt Molnar
quelle
1
Wie funktioniert jQuery? Sie können eine Methode für das jQuery-Objekt wie $ ('. Foo'). Html () aufrufen oder das erste übereinstimmende dom-Element wie $ ('. Foo') [0]
kagronick
1
jQuery ist eine Funktion. Sie übergeben einen Parameter an die $ -Funktion. Daher die () Klammern, nicht []
James Westgate
5

Ein hinterhältiger Weg, dies zu tun, besteht darin, die Sprache selbst zu erweitern.

Schritt 1

Definieren Sie eine benutzerdefinierte Indizierungskonvention. Nennen wir sie "[]".

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

Schritt 2

Definieren Sie eine neue Evaluierungsimplementierung. (Tun Sie dies nicht so, aber es ist ein 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]");

Das Obige funktioniert nicht für komplexere Indizes, kann jedoch mit einer stärkeren Analyse durchgeführt werden.

Alternative:

Anstatt Ihre eigene Obermenge zu erstellen, können Sie stattdessen Ihre Notation mit der vorhandenen Sprache kompilieren und dann bewerten. Dies reduziert den Parsing-Overhead nach der ersten Verwendung auf native.

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
quelle
1
Du bist krumm +1
Jus
absolut widerlich. ich liebe es.
SliceThePi
Klugheit ist normalerweise auf die eine oder andere Weise. Das bedeutet nicht, dass es sich nicht lohnt, sich mit genügend Ressourcen auszuzahlen. Die faulsten Leute sind diejenigen, die ihre eigenen Compiler und Übersetzer schreiben, damit sie in vertrauten Umgebungen arbeiten können, auch wenn sie nicht verfügbar sind. Das heißt, es wäre weniger ekelhaft, wenn es von jemandem mit mehr Erfahrung in der Gegend in weniger Eile geschrieben würde. Alle nicht trivialen Lösungen sind auf die eine oder andere Weise widerlich. Unsere Aufgabe ist es, die Kompromisse zu kennen und mit den Konsequenzen umzugehen.
Dmitry
3

Wir können Proxy bekommen | Methoden direkt einstellen . Davon inspiriert .

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
quelle