Como você sobrecarregaria o operador [] em javascript

86

Não consigo encontrar uma maneira de sobrecarregar o operador [] em javascript. Alguém aí sabe?

Eu estava pensando nas linhas de ...

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

ou não estou olhando para as coisas certas.

slyprid
fonte
3
As respostas aqui estão erradas. Arrays em JS são apenas objetos cujas chaves são coercíveis para valores uint32 (-1) e têm métodos extras em seu protótipo
Benjamin Gruenbaum
Basta fazer do seu MyClassobjeto um array. Você pode copiar as chaves e valores de myArraypara o seu var myObj = new MyClass()objeto.
jpaugh de
ei, gostaria de sobrecarregar a {} operadora, alguma ideia?
daniel assayag

Respostas:

81

Você não pode sobrecarregar operadores em JavaScript.

Foi proposto para ECMAScript 4, mas rejeitado.

Eu não acho que você verá isso tão cedo.

Peter Bailey
fonte
4
Isso já pode ser feito com proxies em alguns navegadores e chegará a todos os navegadores em algum momento. Consulte github.com/DavidBruant/ProxyArray
Tristan,
Então, como jQuery retorna coisas diferentes dependendo se você usa [] ou .eq ()? stackoverflow.com/a/6993901/829305
Rikki
2
Você pode fazer isso agora com um proxy.
Eyal
embora você possa definir métodos com símbolos, contanto que os acesse como um array em vez de com ".". É assim que SmallTalk mapeia coisas Object arg1: a arg2: b arg3: ccomo Object["arg1:arg2:arg3:"](a,b,c). Então você pode ter myObject["[]"](1024): P
Dmitry
O link está
inativo
71

Você pode fazer isso com ES6 Proxy (disponível em todos os navegadores modernos)

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

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

Verifique os detalhes no MDN .

Joe médio
fonte
2
Como usaríamos isso para criar nossa própria classe com um acessador de índice? ou seja, quero usar meu próprio construtor, não quero construir um proxy.
maio
1
Isso não é uma verdadeira sobrecarga. Em vez de chamar métodos no próprio objeto, agora você está chamando métodos do proxy.
Pacerier
@Pacerier você pode retornar target[name]no getter, OP está apenas mostrando os exemplos
Valen
1
Funciona também com o []operador, var key = 'world'; console.log(proxy[key]);
aliás
15

A resposta simples é que o JavaScript permite acesso aos filhos de um objeto por meio dos colchetes.

Então você pode definir sua 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;
};

Você poderá então acessar os membros em qualquer instância de sua classe com qualquer uma das sintaxes.

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
fonte
2
Eu definitivamente não recomendaria isso por motivos de desempenho, mas é a única solução real para o problema aqui. Talvez uma edição afirmando que não é possível tornaria esta uma ótima resposta.
Milimétrico
4
Não é isso que a pergunta quer. A questão é perguntar por uma maneira de detectar o foo['random']que seu código não é capaz de fazer.
Pacerier
9

Use um proxy. Isso foi mencionado em outro lugar nas respostas, mas acho que este é um exemplo melhor:

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
fonte
Provavelmente, vale a pena mencionar que os proxies são um recurso do ES6 e, portanto, têm um suporte de navegador mais limitado (e o Babel também não pode falsificá-los ).
jdehesa de
8

Como o operador colchetes é, na verdade, um operador de acesso à propriedade, você pode conectá-lo com getters e setters. Para o IE, você terá que usar Object.defineProperty (). Exemplo:

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

obj.attr = 123;

O mesmo para o IE8 +:

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

Para IE5-7 há onpropertychangeapenas evento, que funciona para elementos DOM, mas não para outros objetos.

A desvantagem do método é que você só pode conectar solicitações a um conjunto predefinido de propriedades, não a propriedades arbitrárias sem qualquer nome predefinido.

kstep
fonte
Você poderia demonstrar sua abordagem em jsfiddle.net ? Presumo que a solução deve funcionar para qualquer chave na expressão, obj['any_key'] = 123;mas o que vejo em seu código, preciso definir setter / getter para qualquer chave (ainda não conhecida). Isso é impossível.
dma_k
3
mais 1 para compensar o menos 1 porque não se trata apenas do IE.
orbe
Isso poderia ser feito para uma função de classe? Estou lutando para encontrar a sintaxe para isso sozinho.
Michael Hoffmann
7

Você precisa usar Proxy conforme explicado, mas ele pode ser integrado a um construtor de classe

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

com isso'. Em seguida, as funções set e get (também deleteProperty) serão acionadas. Embora você obtenha um objeto Proxy que parece diferente, na maioria das vezes funciona perguntar a comparação (target.constructor === MyClass) é o tipo de classe etc. [mesmo que seja uma função onde target.constructor.name é o nome da classe em texto (apenas observando um exemplo de coisas que funcionam ligeiramente diferentes.)]

Mestre James
fonte
1
Isso funciona bem para sobrecarregar o [] em uma coleção de Backbone de modo que os objetos de modelo individuais sejam retornados ao usar [] com uma passagem para todas as outras propriedades.
justkt
5

Então, você espera fazer algo como var what = MyClassInstance [4]; ? Em caso afirmativo, a resposta simples é que o Javascript não suporta atualmente a sobrecarga do operador.

Matt Molnar
fonte
1
Então, como funciona o jQuery. Você poderia chamar um método no objeto jQuery como $ ('. Foo'). Html () ou obter o primeiro elemento dom correspondente como $ ('. Foo') [0]
kagronick
1
jQuery é uma função, você está passando um parâmetro para a função $. Daí os colchetes (), não []
James Westgate
5

uma maneira sorrateira de fazer isso é estendendo a própria linguagem.

passo 1

definir uma convenção de indexação customizada, vamos chamá-la de "[]".

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

definir uma nova implementação de avaliação. (não faça assim, mas é uma prova de conceito).

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

o acima não funcionará para índices mais complexos, mas pode ser com uma análise mais forte.

alternativo:

em vez de recorrer à criação de sua própria linguagem de superconjunto, você pode compilar sua notação para a linguagem existente e, em seguida, avaliá-la. Isso reduz a sobrecarga de análise para nativa após a primeira vez que você o 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
fonte
3
Você é desonesto +1
Jus
1
absolutamente nojento. eu amo isso.
SliceThePi
A inteligência geralmente ocorre de uma forma ou de outra. Isso não significa que não seja um exercício que valha a pena, que pode ser recompensado com recursos suficientes. As pessoas mais preguiçosas são aquelas que escrevem seus próprios compiladores e tradutores apenas para que possam trabalhar em ambientes mais familiares, mesmo que não estejam disponíveis. Dito isso, seria menos nojento se fosse escrito com menos pressa por alguém com mais experiência na área. Todas as soluções não triviais são nojentas de uma forma ou de outra, nosso trabalho é conhecer as vantagens e desvantagens e lidar com as consequências.
Dmitry
3

Podemos obter proxy | definir métodos diretamente. Inspirado por isso .

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
fonte