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?


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

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.


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.


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


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.


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


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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.