Jak można przeciążyć operator [] w javascript

85

Nie mogę znaleźć sposobu na przeciążenie operatora [] w javascript. Czy ktoś tam wie?

Myślałem na wzór ...

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

czy też nie patrzę na właściwe rzeczy.

slyprid
źródło
3
Tutejsze odpowiedzi są błędne, Tablice w JS są tylko obiekty, których klucze są coercable do UInt32 (- 1) wartości i mają dodatkowe sposoby na ich pierwowzór
Benjamin Gruenbaum
Po prostu utwórz z MyClassobiektu tablicę. Możesz skopiować klucze i wartości z myArraydo swojego var myObj = new MyClass()obiektu.
jpaugh
hej, chciałbym przeładować operator {}, masz jakiś pomysł?
Daniel Assayag

Odpowiedzi:

81

Nie możesz przeciążać operatorów w JavaScript.

Został zaproponowany dla ECMAScript 4, ale został odrzucony.

Myślę, że nie zobaczysz tego w najbliższym czasie.

Peter Bailey
źródło
4
Może to być możliwe w przypadku serwerów proxy już w niektórych przeglądarkach i w pewnym momencie będzie dostępne we wszystkich przeglądarkach. Zobacz github.com/DavidBruant/ProxyArray
Tristan
W takim razie w jaki sposób jQuery zwraca różne rzeczy w zależności od tego, czy używasz [] czy .eq ()? stackoverflow.com/a/6993901/829305
Rikki
2
Możesz to zrobić teraz za pomocą serwera proxy.
Eyal
chociaż możesz definiować metody z symbolami, o ile masz do nich dostęp jako tablicę, a nie za pomocą „.”. To jak SmallTalk odwzorowuje takie rzeczy Object arg1: a arg2: b arg3: cjak Object["arg1:arg2:arg3:"](a,b,c). Możesz więc mieć myObject["[]"](1024): P
Dmitry
Link nie działa :(
Gp2mv3
69

Możesz to zrobić za pomocą ES6 Proxy (dostępne we wszystkich nowoczesnych przeglądarkach)

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

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

Sprawdź szczegóły na MDN .

przeciętny Joe
źródło
2
Jak użylibyśmy tego do stworzenia własnej klasy z akcesorem indeksu? tj. chcę użyć własnego konstruktora, nie chcę konstruować proxy.
mpen
1
To nie jest prawdziwe przeciążenie. Zamiast wywoływać metody na samym obiekcie, teraz wywołujesz metody proxy.
Pacerier
@Pacerier możesz zwrócić target[name]w getterze , OP pokazuje tylko przykłady
Valen
1
Współpracuje również z []operatorem, przy okazji:var key = 'world'; console.log(proxy[key]);
Klesun
15

Prosta odpowiedź jest taka, że ​​JavaScript umożliwia dostęp do elementów potomnych Object poprzez nawiasy kwadratowe.

Możesz więc zdefiniować swoją klasę:

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

Będziesz wtedy mógł uzyskać dostęp do członków we wszystkich wystąpieniach swojej klasy za pomocą dowolnej składni.

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
źródło
2
zdecydowanie nie polecałbym tego ze względu na wydajność, ale jest to jedyne rzeczywiste rozwiązanie tego problemu. Być może zmiana stwierdzająca, że ​​to niemożliwe, uczyniłaby to świetną odpowiedzią.
Milimetryczny
4
To nie jest to, co chce się pytanie. Pytanie dotyczy sposobu wyłapania, foo['random']którego twój kod nie jest w stanie zrobić.
Pacerier
9

Użyj proxy. Wspomniano o tym w innym miejscu odpowiedzi, ale myślę, że to jest lepszy przykład:

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
źródło
Prawdopodobnie warto wspomnieć, że serwery proxy są funkcją ES6 i dlatego mają bardziej ograniczoną obsługę przeglądarki (a Babel również nie może ich sfałszować ).
jdehesa
8

Ponieważ operator nawiasów jest w rzeczywistości operatorem dostępu do właściwości, można go podłączyć za pomocą metod pobierających i ustawiających. W przypadku IE będziesz musiał zamiast tego użyć Object.defineProperty (). Przykład:

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

obj.attr = 123;

To samo dla IE8 +:

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

W IE5-7 istnieje onpropertychangetylko zdarzenie, które działa dla elementów DOM, ale nie dla innych obiektów.

Wadą tej metody jest to, że można podpiąć żądania tylko do wstępnie zdefiniowanego zestawu właściwości, a nie do dowolnej właściwości bez wcześniej zdefiniowanej nazwy.

kstep
źródło
Czy mógłbyś zaprezentować swoje podejście na jsfiddle.net ? Zakładam, że rozwiązanie powinno działać dla dowolnego klucza w wyrażeniu, obj['any_key'] = 123;ale to, co widzę w Twoim kodzie, muszę zdefiniować setter / getter dla dowolnego (jeszcze nieznanego) klucza. To jest niemożliwe.
dma_k
3
plus 1, aby skompensować minus 1, ponieważ nie jest to tylko IE.
orb
Czy można to zrobić dla funkcji klasowej? Sam staram się znaleźć odpowiednią składnię.
Michael Hoffmann
7

Musisz użyć Proxy, jak wyjaśniono, ale ostatecznie można go zintegrować z konstruktorem klasy

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

z tym'. Następnie uruchomione zostaną funkcje set i get (także deleteProperty). Chociaż otrzymujesz obiekt Proxy, który wydaje się inny, w większości przypadków działa on w celu zapytania porównania (target.constructor === MyClass) o typ klasy itd. [Nawet jeśli jest to funkcja, w której target.constructor.name jest nazwą klasy w tekst (wystarczy podać przykład rzeczy, które działają nieco inaczej)]

Mistrz James
źródło
1
Działa to dobrze w przypadku przeciążania [] w kolekcji Backbone, tak aby poszczególne obiekty modelu były zwracane przy użyciu [] z tranzytem dla wszystkich innych właściwości.
justkt
5

Więc masz nadzieję zrobić coś takiego jak var cokolwiek = MyClassInstance [4]; ? Jeśli tak, prostą odpowiedzią jest to, że JavaScript nie obsługuje obecnie przeciążania operatorów.

Matt Molnar
źródło
1
Jak więc działa jQuery. Możesz wywołać metodę w obiekcie jQuery, taką jak $ ('. Foo'). Html () lub pobrać pierwszy pasujący element dom, taki jak $ ('. Foo') [0]
kagronick
1
jQuery to funkcja, przekazujesz parametr do funkcji $. Stąd () nawiasy, a nie []
James Westgate,
5

jednym z podstępnych sposobów jest rozszerzenie samego języka.

krok 1

zdefiniuj niestandardową konwencję indeksowania, nazwijmy ją „[]”.

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

krok 2

zdefiniować nową implementację oceny. (nie rób tego w ten sposób, ale jest to dowód słuszności koncepcji).

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

powyższe nie zadziała dla bardziej złożonych indeksów, ale może być przy silniejszym parsowaniu.

alternatywny:

zamiast uciekać się do tworzenia własnego supersetu języka, możesz zamiast tego skompilować swoją notację do istniejącego języka, a następnie ją ewaluować. Zmniejsza to obciążenie parsowania do natywnego po pierwszym użyciu.

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));
Dmitrij
źródło
1
Jesteś oszukany +1
Jus
absolutnie obrzydliwe. kocham to.
SliceThePi,
Spryt jest zwykle taki czy inny. Nie oznacza to, że nie jest to opłacalne ćwiczenie, które może się opłacić przy wystarczających zasobach. Najbardziej leniwi ludzie to ci, którzy piszą własne kompilatory i tłumacze tylko po to, by móc pracować w bardziej znanych środowiskach, nawet jeśli nie są dostępne. To powiedziawszy, byłoby mniej obrzydliwe, gdyby zostało napisane w mniejszym pośpiechu przez kogoś z większym doświadczeniem w tej dziedzinie. Wszystkie nietrywialne rozwiązania są w taki czy inny sposób obrzydliwe, nasza praca polega na znajomości kompromisów i radzeniu sobie z konsekwencjami.
Dmitry
3

Możemy proxy GET | ustaw metody bezpośrednio. Zainspirowany tym .

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
źródło