web-dev-qa-db-fra.com

Comment surchargeriez-vous l'opérateur [] en javascript

Je n'arrive pas à trouver le moyen de surcharger l'opérateur [] en javascript. Quelqu'un sait-il?

Je pensais sur les lignes de ...

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

ou est-ce que je ne regarde pas les bonnes choses.

70
slyprid

Vous ne pouvez pas surcharger les opérateurs en JavaScript.

C'était proposé pour ECMAScript 4 mais rejeté.

Je ne pense pas que vous le verrez de sitôt.

73
Peter Bailey

Vous pouvez le faire avec ES6 Proxy (disponible dans tous les navigateurs modernes)

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

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

Vérifiez les détails sur MDN .

53
average Joe

La réponse simple est que JavaScript permet d'accéder aux enfants d'un objet via les crochets.

Vous pouvez donc définir votre 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;
};

Vous pourrez ensuite accéder aux membres sur toutes les instances de votre classe avec l'une ou l'autre syntaxe.

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
15
Brandon McKinney

Utilisez un proxy. Cela a été mentionné ailleurs dans les réponses, mais je pense que c'est un meilleur exemple:

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.
8
Eyal

Comme l'opérateur des crochets est en fait un opérateur d'accès à la propriété, vous pouvez l'accrocher avec des getters et des setters. Pour IE vous devrez utiliser Object.defineProperty () à la place. Exemple:

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

obj.attr = 123;

La même chose pour IE8 +:

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

Pour IE5-7, il n'y a que l'événement onpropertychange, qui fonctionne pour les éléments DOM, mais pas pour les autres objets.

L'inconvénient de la méthode est que vous ne pouvez accrocher les demandes qu'à un ensemble de propriétés prédéfini, et non à une propriété arbitraire sans nom prédéfini.

7
kstep

Vous espérez donc faire quelque chose comme var quel que soit = ​​MyClassInstance [4]; ? Si c'est le cas, la réponse est simple: Javascript ne prend actuellement pas en charge la surcharge des opérateurs.

5
Matt Molnar

Vous devez utiliser le proxy comme expliqué, mais il peut finalement être intégré dans une classe constructeur

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

avec ça'. Ensuite, les fonctions set et get (également deleteProperty) se déclencheront. Bien que vous obteniez un objet Proxy qui semble différent, il fonctionne pour la plupart à demander au compare (target.constructor === MyClass) son type de classe, etc. [même si c'est une fonction où target.constructor.name est le nom de classe dans texte (juste en notant un exemple de choses qui fonctionnent légèrement différemment.)]

5
Master James

une façon sournoise de le faire est d'étendre la langue elle-même.

étape 1

définir une convention d'indexation personnalisée, appelons-la, "[]".

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

étape 2

définir une nouvelle implémentation eval. (ne faites pas de cette façon, mais c'est une preuve de 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]");

ce qui précède ne fonctionnera pas pour les index plus complexes, mais cela peut être avec une analyse plus forte.

alternative:

au lieu de recourir à la création de votre propre langage de sur-ensemble, vous pouvez plutôt compiler votre notation dans le langage existant, puis l’évaluer. Cela réduit la surcharge d'analyse à native après la première utilisation.

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

Nous pouvons proxy obtenir | définissez les méthodes directement. Inspiré par 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]
0
Jaber