Chamando uma função sem parênteses

275

Disseram-me hoje que é possível invocar uma função sem parênteses. As únicas maneiras que eu conseguia pensar era usando funções como applyou call.

f.apply(this);
f.call(this);

Mas isso exige parênteses applye callnos deixa na estaca zero. Também considerei a ideia de passar a função para algum tipo de manipulador de eventos, como setTimeout:

setTimeout(f, 500);

Mas então a pergunta se torna "como você invoca setTimeoutsem parênteses?"

Então, qual é a solução para esse enigma? Como você pode chamar uma função em Javascript sem usar parênteses?

Mike Cluck
fonte
59
Não estou tentando ser rude, mas devo perguntar: por que?
jehna1
36
@ jehna1 Porque eu estava respondendo uma pergunta sobre como as funções são invocadas e foi corrigida quando eu disse que elas exigiam parênteses no final.
Mike Cluck
3
Surpreendente! Eu não imaginava esta pergunta teria que muita tração .. Talvez eu devesse ter perguntado por mim mesmo :-)
Amit
5
Este é o tipo de pergunta que parte meu coração. Que bem poderia advir de tal abuso e exploração? Quero conhecer um caso de uso válido em que <insert any solution>seja objetivamente melhor ou mais útil que f().
Obrigado
5
@ Aaron: você diz que não há uma razão válida, mas, como aponta a resposta de Alexander O'Mara , pode-se considerar a questão de saber se a remoção de colchetes é suficiente para impedir a execução do código - ou o que acontece com uma competição Javascript ofuscada? É claro que é um objetivo absurdo ao tentar escrever código de manutenção, mas é uma pergunta divertida.
PJTraill

Respostas:

429

Existem várias maneiras diferentes de chamar uma função sem parênteses.

Vamos supor que você tenha essa função definida:

function greet() {
    console.log('hello');
}

A seguir, siga algumas maneiras de ligar greetsem parênteses:

1. Como Construtor

Com newvocê pode invocar uma função sem parênteses:

new greet; // parentheses are optional in this construct.

Do MDN no newoprator :

Sintaxe

new constructor[([arguments])]

2. Como toStringou valueOfImplementação

toStringe valueOfsão métodos especiais: eles são chamados implicitamente quando uma conversão é necessária:

var obj = {
    toString: function() {
         return 'hello';
    }
}

'' + obj; // concatenation forces cast to string and call to toString.

Você poderia (ab) usar esse padrão para chamar greetsem parênteses:

'' + { toString: greet };

Ou com valueOf:

+{ valueOf: greet };

valueOfe toStringsão chamados de fato do método @@ toPrimitive (desde ES6) e, portanto, você também pode implementar esse método:

+{ [Symbol.toPrimitive]: greet }
"" + { [Symbol.toPrimitive]: greet }

2.b Substituição valueOfno protótipo de função

Você pode seguir a ideia anterior para substituir o valueOfmétodo no Functionprotótipo :

Function.prototype.valueOf = function() {
    this.call(this);
    // Optional improvement: avoid `NaN` issues when used in expressions.
    return 0; 
};

Depois de fazer isso, você pode escrever:

+greet;

E embora haja parênteses envolvidos na linha, a chamada de acionamento real não tem parênteses. Veja mais sobre isso no blog "Chamando métodos em JavaScript, sem realmente chamá-los"

3. Como gerador

Você pode definir uma função de gerador (com *), que retorna um iterador . Você pode chamá-lo usando a sintaxe de propagação ou com a for...ofsintaxe.

Primeiro, precisamos de uma variante geradora da greetfunção original :

function* greet_gen() {
    console.log('hello');
}

E então a chamamos sem parênteses, definindo o método @@ iterator :

[...{ [Symbol.iterator]: greet_gen }];

Normalmente, os geradores teriam uma yieldpalavra - chave em algum lugar, mas não é necessário para a função ser chamada.

A última instrução chama a função, mas isso também pode ser feito com a desestruturação :

[,] = { [Symbol.iterator]: greet_gen };

ou uma for ... ofconstrução, mas possui parênteses próprios:

for ({} of { [Symbol.iterator]: greet_gen });

Observe que você também pode fazer o acima com a greetfunção original , mas ela acionará uma exceção no processo, após a greet execução (testada no FF e no Chrome). Você pode gerenciar a exceção com um try...catchbloco.

4. Como Getter

@ jehna1 tem uma resposta completa sobre isso, então dê-lhe crédito. Aqui está uma maneira de chamar uma função sem parênteses no escopo global, evitando o método descontinuado__defineGetter__ . Ele usa em seu Object.definePropertylugar.

Precisamos criar uma variante da greetfunção original para isso:

Object.defineProperty(window, 'greet_get', { get: greet });

E depois:

greet_get;

Substitua windowpor qualquer que seja seu objeto global.

Você poderia chamar a greetfunção original sem deixar rastros no objeto global como este:

Object.defineProperty({}, 'greet', { get: greet }).greet;

Mas alguém poderia argumentar que temos parênteses aqui (embora eles não estejam envolvidos na invocação real).

5. Como função de tag

Desde o ES6, você pode chamar uma função passando um literal de modelo com esta sintaxe:

greet``;

Consulte "Literais de modelo marcados" .

6. Como manipulador de proxy

Desde o ES6, você pode definir um proxy :

var proxy = new Proxy({}, { get: greet } );

E a leitura de qualquer valor da propriedade chamará greet:

proxy._; // even if property not defined, it still triggers greet

Existem muitas variações disso. Mais um exemplo:

var proxy = new Proxy({}, { has: greet } );

1 in proxy; // triggers greet

7. Como verificador de instância

O instanceofoperador executa o @@hasInstancemétodo no segundo operando, quando definido:

1 instanceof { [Symbol.hasInstance]: greet } // triggers greet
trincot
fonte
1
Essa é uma abordagem muito interessante usando valueOf.
Mike Cluck
4
Você esqueceu o ES6:func``;
Ismael Miguel
1
Você usou suportes chamando eval :)
trincot
1
Nesse caso, a função não é executada, mas passada para o chamador.
trincot
1
@trincot Outro método será possível em breve com o operador de pipeline :'1' |> alert
deniro
224

A maneira mais fácil de fazer isso é com o newoperador:

function f() {
  alert('hello');
}

new f;

Embora isso não seja ortodoxo nem natural, ele funciona e é perfeitamente legal.

O newoperador não exige parênteses se nenhum parâmetro for usado.

Amit
fonte
6
Correção, a maneira mais fácil de fazer isso é preceder um operando que força a expressão da função a ser avaliada. !fresulta na mesma coisa, sem instanciar uma instância da função construtora (que requer a criação de um novo contexto). É muito mais eficiente e é usado em muitas bibliotecas populares (como o Bootstrap).
THEtheChad 13/03/16
6
@THEtheChad - avaliar uma expressão não é o mesmo que executar uma função, mas se você tiver um método diferente (melhor? Mais surpreendente?) De invocar (causar execução) de uma função - poste uma resposta!
Amit
O ponto de prepending um ponto de exclamação, ou qualquer outro caractere único operador unário ( +, -, ~) em tais bibliotecas, é para salvar um byte na versão minimizada da biblioteca onde o valor de retorno não é necessário. (ou seja !function(){}()) A sintaxe usual de auto-chamada é (function(){})()(infelizmente a sintaxe function(){}()não é entre navegadores) Como a função de auto-chamada mais usada geralmente não tem nada a retornar, geralmente é o objetivo dessa técnica de economia de bytes
Ultimater
1
@THEtheChad Isso é verdade? Eu apenas tentei no Chrome e não estava obtendo execução de funções. function foo() { alert("Foo!") };Por exemplo: !fooretornafalse
Glenn 'devalias'
95

Você pode usar getters e setters.

var h = {
  get ello () {
    alert("World");
  }
}

Execute este script apenas com:

h.ello  // Fires up alert "world"

Editar:

Podemos até argumentar!

var h = {
  set ello (what) {
    alert("Hello " + what);
  }
}

h.ello = "world" // Fires up alert "Hello world"

Edição 2:

Você também pode definir funções globais que podem ser executadas sem parênteses:

window.__defineGetter__("hello", function() { alert("world"); });
hello;  // Fires up alert "world"

E com argumentos:

window.__defineSetter__("hello", function(what) { alert("Hello " + what); });
hello = "world";  // Fires up alert "Hello world"

Aviso Legal:

Como o @MonkeyZeus declarou: Nunca, você nunca deve usar esse código na produção, por melhores que sejam suas intenções.

jehna1
fonte
9
Estritamente falando no ponto de vista de "Eu sou um machado empunhando um louco que tem a honra de assumir o seu código porque você mudou para coisas maiores e melhores; mas eu sei onde você mora". Eu realmente espero que isso não seja normal, lol. Utilizá-lo dentro de um plug-in que você mantém, aceitável. Escrever código de lógica de negócios, por favor, mantenha enquanto eu afiar meu machado :)
MonkeyZeus
3
@MonkeyZeus haha, sim! Adicionado um aviso para codificadores do futuro que pode encontrar este do Google
jehna1
Na verdade, este aviso é muito duro. Existem casos de uso legítimos para "getter como uma chamada de função". De fato, é amplamente utilizado em uma biblioteca de muito sucesso. (você gosta uma dica ?? :-)
Amit
1
Observe que __defineGetter__está obsoleto. Use em Object.definePropertyvez disso.
Felix Kling
2
@MonkeyZeus - como escrevi explicitamente no comentário - esse estilo de chamada de função está sendo usado , extensivamente e intencionalmente, em uma biblioteca pública de muito sucesso como a própria API, não internamente.
Amit
23

Aqui está um exemplo para uma situação específica:

window.onload = funcRef;

Embora essa declaração não seja realmente invocadora, ela levará a uma invocação futura .

Mas acho que as áreas cinzentas podem ser boas para enigmas como este :)

Jonathan.Brink
fonte
6
Isso é legal, mas não é JavaScript, é DOM.
Amit
6
@Mit, o DOM faz parte do ambiente JS do navegador, que ainda é JavaScript.
zzzzBov
3
@zzzzBov Nem todo o ECMAScript é executado em um navegador da web. Ou você está entre as pessoas que usam "JavaScript" para se referir especificamente à combinação do ES com o HTML DOM, em oposição ao Node?
Damian Yerrick
9
@DamianYerrick, considero o DOM uma biblioteca disponível em alguns ambientes JS, que não o torna menos JavaScript do que o sublinhado disponível em alguns ambientes. Ainda é JavaScript, não é apenas uma parte especificada na especificação ECMAScript.
zzzzBov
3
@zzzzBov, "Embora o DOM seja frequentemente acessado usando JavaScript, ele não faz parte da linguagem JavaScript. Também pode ser acessado por outros idiomas." . Por exemplo, o FireFox usa XPIDL e XPCOM para implementação em DOM. Não é tão evidente que a chamada da função de retorno de chamada especificada é implementada em JavaScript. O DOM não é uma API como outras bibliotecas JavaScript.
trincot
17

Se aceitarmos uma abordagem de pensamento lateral, em um navegador, existem várias APIs que podemos abusar para executar JavaScript arbitrário, incluindo a chamada de uma função, sem caracteres reais entre parênteses.

1. locatione javascript:protocolo:

Uma dessas técnicas é abusar do javascript:protocolo na locationatribuição.

Exemplo de trabalho:

location='javascript:alert\x281\x29'

Embora tecnicamente \x28 e \x29ainda estejam entre parênteses após a avaliação do código, o caractere (e o real )não aparecem. Os parênteses são escapados em uma sequência de JavaScript que é avaliada na atribuição.


2. onerrore eval:

Da mesma forma, dependendo do navegador, podemos abusar do global onerror, definindo-o como evale lançando algo que será restringido ao JavaScript válido. Este é mais complicado, porque os navegadores são inconsistentes nesse comportamento, mas aqui está um exemplo para o Chrome.

Exemplo de trabalho para o Chrome (não Firefox, outros não testados):

window.onerror=eval;Uncaught=0;throw';alert\x281\x29';

Isso funciona no Chrome porque throw'test'passará 'Uncaught test'como o primeiro argumento paraonerror , que é o JavaScript quase válido. Se fizermos isso, throw';test'isso passará 'Uncaught ;test'. Agora temos JavaScript válido! Basta definir Uncaughte substituir o teste pela carga útil.


Em conclusão:

Esse código é realmente horrível e nunca deve ser usado , mas às vezes é usado em ataques de XSS; portanto, a moral da história é não confiar na filtragem de parênteses para impedir o XSS. Usar um CSP para impedir esse código também seria uma boa ideia.

Alexander O'Mara
fonte
Não é o mesmo que usar evale escrever a sequência sem realmente usar os caracteres entre parênteses.
Zev Spitz
@ZenSpitz Sim, mas evalnormalmente requer parênteses, portanto, este truque de evasão de filtro.
Alexander O'Mara 14/03
5
Indiscutivelmente \x28e \x29ainda são parênteses. '\x28' === '('.
trincot
@trincot Qual é o tipo de argumento abordado na resposta, é uma abordagem de pensamento lateral que mostra uma razão pela qual isso pode ser feito. Eu deixei isso mais claro na resposta.
Alexander O'Mara
Além disso, quem votou para excluir isso, revise as diretrizes para excluir respostas .
Alexander O'Mara
1

No ES6, você tem o que chamamos literais de modelo marcado .

Por exemplo:

function foo(val) {
    console.log(val);
}

foo`Tagged Template Literals`;

Idan Dagan
fonte
3
Na verdade, essa é a resposta que eu postei 1,5 anos antes da sua ... não sei por que ela merece uma nova resposta?
precisa saber é
2
porque outras pessoas que desejam implementar a mesma coisa agora podem usar a nova resposta.
mehta-rohan 5/09
3
@ mehta-rohan, eu não entendo esse comentário. Se isso faria sentido, por que não duplicar a mesma resposta repetidamente? Não vejo como isso seria útil.
trincot