Invocar una función sin paréntesis

275

Hoy me dijeron que es posible invocar una función sin paréntesis. Las únicas formas en que podía pensar era usando funciones como applyo call.

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

Pero estos requieren paréntesis applyy callnos dejan en el punto de partida. También consideré la idea de pasar la función a algún tipo de controlador de eventos como setTimeout:

setTimeout(f, 500);

Pero entonces la pregunta se convierte en "¿cómo invocar setTimeoutsin paréntesis?"

Entonces, ¿cuál es la solución a este acertijo? ¿Cómo se puede invocar una función en Javascript sin usar paréntesis?

Mike Cluck
fuente
59
No trato de ser grosero, pero debo preguntar: ¿Por qué?
jehna1
36
@ jehna1 Porque estaba respondiendo una pregunta sobre cómo se invocan las funciones y se corrigió cuando dije que requerían paréntesis al final.
Mike Cluck
3
¡Asombroso! No me imaginé que esta pregunta tuviera tanta tracción ... Tal vez debería haberla hecho yo mismo :-)
Amit
55
Este es el tipo de pregunta que me rompe el corazón. ¿De qué podría servir un abuso y una explotación así? Quiero saber un caso de uso válido donde <insert any solution>es objetivamente mejor o más útil que f().
Gracias
55
@ Aaron: usted dice que no hay una razón válida, pero, como señala la respuesta de Alexander O'Mara , uno puede estar considerando la cuestión de si la eliminación de los corchetes es suficiente para evitar la ejecución del código, o ¿qué pasa con una competencia Javascript ofuscada? Por supuesto, es un objetivo absurdo cuando se intenta escribir código mantenible, pero es una pregunta divertida.
PJTraill

Respuestas:

429

Hay varias formas diferentes de llamar a una función sin paréntesis.

Supongamos que tiene esta función definida:

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

A continuación, siga algunas formas de llamar greetsin paréntesis:

1. Como constructor

Con newusted puede invocar una función sin paréntesis:

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

De MDN en el newoprador :

Sintaxis

new constructor[([arguments])]

2. Como toStringo valueOfImplementación

toStringy valueOfson métodos especiales: se les llama implícitamente cuando es necesaria una conversión:

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

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

Podrías (ab) usar este patrón para llamar greetsin paréntesis:

'' + { toString: greet };

O con valueOf:

+{ valueOf: greet };

valueOfy toStringde hecho son llamados desde el método @@ toPrimitive (desde ES6), por lo que también puede implementar ese método:

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

2.b Anulación valueOfde prototipo de función

Podría tomar la idea anterior para anular el valueOfmétodo en el Functionprototipo :

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

Una vez que haya hecho eso, puede escribir:

+greet;

Y aunque hay paréntesis involucrados en la línea, la invocación de activación real no tiene paréntesis. Vea más sobre esto en el blog "Métodos de llamada en JavaScript, sin realmente llamarlos"

3. Como generador

Podría definir una función generadora (con *), que devuelve un iterador . Puede llamarlo usando la sintaxis de propagación o con la for...ofsintaxis.

Primero necesitamos una variante de generador de la greetfunción original :

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

Y luego lo llamamos sin paréntesis definiendo el método de iterador @@ :

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

Normalmente los generadores tendrían una yieldpalabra clave en alguna parte, pero no es necesaria para que se llame a la función.

La última declaración invoca la función, pero eso también podría hacerse con la desestructuración :

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

o una for ... ofconstrucción, pero tiene paréntesis propios:

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

Tenga en cuenta que también puede hacer lo anterior con la greetfunción original , pero provocará una excepción en el proceso, después de que greet se haya ejecutado (probado en FF y Chrome). Puede gestionar la excepción con un try...catchbloque.

4. Como Getter

@ jehna1 tiene una respuesta completa sobre esto, así que dale crédito. Aquí hay una manera de llamar a una función sin paréntesis en el ámbito global, evitando el método obsoleto__defineGetter__ . Utiliza en su Object.definePropertylugar.

Necesitamos crear una variante de la greetfunción original para esto:

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

Y entonces:

greet_get;

Reemplace windowcon cualquiera que sea su objeto global.

Puede llamar a la greetfunción original sin dejar un rastro en el objeto global como este:

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

Pero uno podría argumentar que tenemos paréntesis aquí (aunque no están involucrados en la invocación real).

5. Como función de etiqueta

Desde ES6 puede llamar a una función pasándole un literal de plantilla con esta sintaxis:

greet``;

Consulte "Literales de plantillas etiquetadas" .

6. Como controlador proxy

Desde ES6, puede definir un proxy :

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

Y luego leer cualquier valor de propiedad invocará greet:

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

Hay muchas variaciones de esto. Un ejemplo más:

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

1 in proxy; // triggers greet

7. Como verificador de instancias

El instanceofoperador ejecuta el @@hasInstancemétodo en el segundo operando, cuando se define:

1 instanceof { [Symbol.hasInstance]: greet } // triggers greet
trincot
fuente
1
Ese es un enfoque muy interesante usando valueOf.
Mike Cluck
44
Te olvidaste de ES6:func``;
Ismael Miguel
1
Usaste
1
En ese caso, la función no se ejecuta, sino que se pasa al llamante.
trincot
1
@trincot Pronto será posible otro método con el operador de la tubería :'1' |> alert
deniro
224

La forma más fácil de hacerlo es con el newoperador:

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

new f;

Si bien eso es poco ortodoxo y antinatural, funciona y es perfectamente legal.

El newoperador no requiere paréntesis si no se utilizan parámetros.

Amit
fuente
66
Corrección, la forma más fácil de hacer esto es anteponer un operando que obliga a evaluar la expresión de la función. !fda como resultado lo mismo, sin instanciar una instancia de la función constructora (que requiere crear un nuevo contexto). Es mucho más eficiente y se usa en muchas bibliotecas populares (como Bootstrap).
THEtheChad
66
@THEtheChad: evaluar una expresión no es lo mismo que ejecutar una función, pero si tiene un método diferente (¿más agradable? ¿Más sorprendente?) Para invocar (provocar la ejecución) de una función, ¡publique una respuesta!
Amit
El punto de anteponiendo un signo de exclamación, o cualquier otro carácter único operador unario ( +, -, ~) en este tipo de bibliotecas, es salvar un byte en la versión minimizada de la biblioteca en la que no se necesita el valor de retorno. (es decir !function(){}()) La sintaxis de auto-llamada habitual es (function(){})()(desafortunadamente, la sintaxis function(){}()no es de navegador cruzado) Dado que la función de auto-llamada más alta generalmente no tiene nada que devolver, generalmente es el objetivo de esta técnica de guardado de bytes
Ultimater
1
@THEtheChad ¿Es esto cierto? Acabo de probarlo en Chrome y no estaba ejecutando la función. function foo() { alert("Foo!") };Por ejemplo: !foovuelvefalse
Glenn 'devalias'
95

Puedes usar getters y setters.

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

Ejecute este script solo con:

h.ello  // Fires up alert "world"

Editar:

¡Incluso podemos hacer argumentos!

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

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

Edición 2:

También puede definir funciones globales que se pueden ejecutar sin paréntesis:

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

Y con argumentos:

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

Descargo de responsabilidad:

Como dijo @MonkeyZeus: nunca usarás este código en producción, sin importar cuán buenas sean tus intenciones.

jehna1
fuente
9
Hablando estrictamente desde el punto de vista de "Soy un loco que empuña un hacha que tiene el honor de hacerse cargo de tu código porque te has mudado a cosas más grandes y mejores; sin embargo, sé dónde vives". Realmente espero que esto no sea normal jajaja. Usarlo dentro de un complemento que usted mantiene, aceptable. Escribir código de lógica de negocios
espere
3
@MonkeyZeus jaja, sí! Se agregó un descargo de responsabilidad para los codificadores del futuro que pueden encontrar esto en Google
jehna1
En realidad, este descargo de responsabilidad es demasiado duro. Existen casos de uso legítimos para "getter como función de llamada". De hecho, se usa ampliamente en una biblioteca muy exitosa. (¿Quieres una pista? :-)
Amit
1
Tenga en cuenta que __defineGetter__está en desuso. Usar en su Object.definePropertylugar.
Felix Kling
2
@MonkeyZeus, como escribí explícitamente en el comentario, este estilo de invocación de funciones se está utilizando , de manera extensa e intencional, en una biblioteca pública muy exitosa como la API en sí, no internamente.
Amit
23

Aquí hay un ejemplo para una situación particular:

window.onload = funcRef;

Aunque esa declaración en realidad no se invoca, dará lugar a una futura invocación .

Pero, creo que las áreas grises podrían estar bien para acertijos como este :)

Jonathan.Brink
fuente
66
Eso es bueno, pero eso no es JavaScript, es DOM.
Amit
66
@Amit, el DOM es parte del entorno JS del navegador, que sigue siendo JavaScript.
zzzzBov
3
@zzzzBov No todo ECMAScript se ejecuta en un navegador web. ¿O está entre las personas que usan "JavaScript" para referirse específicamente a la combinación de ES con HTML DOM, en lugar de Nodo?
Damian Yerrick
9
@DamianYerrick, considero que el DOM es una biblioteca disponible en algunos entornos JS, lo que no hace que sea menos JavaScript que el subrayado que está disponible en algunos entornos. Todavía es JavaScript, simplemente no es una parte especificada en la especificación ECMAScript.
zzzzBov
3
@zzzzBov, "Aunque a menudo se accede al DOM usando JavaScript, no es parte del lenguaje JavaScript. También se puede acceder a él desde otros idiomas". . Por ejemplo, FireFox usa XPIDL y XPCOM para la implementación DOM. No es tan evidente que la llamada de la función de devolución de llamada especificada se implemente en JavaScript. El DOM no es una API como otras bibliotecas de JavaScript.
Trincot
17

Si aceptamos un enfoque de pensamiento lateral, en un navegador hay varias API que podemos abusar para ejecutar JavaScript arbitrario, incluida la llamada a una función, sin ningún paréntesis real.

1. locationy javascript:protocolo:

Una de esas técnicas es abusar del javascript:protocolo en la locationasignación.

Ejemplo de trabajo

location='javascript:alert\x281\x29'

Aunque técnicamente \x28 y \x29aún son paréntesis una vez que se evalúa el código, el carácter real (y el )carácter no aparecen. Los paréntesis se escapan en una cadena de JavaScript que se evalúa en la asignación.


2. onerrory eval:

De manera similar, dependiendo del navegador, podemos abusar de lo global onerror, configurándolo evaly arrojando algo que se convertirá en cadena a JavaScript válido. Este es más complicado, porque los navegadores son inconsistentes en este comportamiento, pero aquí hay un ejemplo para Chrome.

Ejemplo de trabajo para Chrome (no Firefox, otros no probados):

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

Esto funciona en Chrome porque throw'test'pasará 'Uncaught test'como primer argumento a onerror, que es JavaScript casi válido. Si en cambio lo hacemos throw';test', pasará 'Uncaught ;test'. ¡Ahora tenemos JavaScript válido! Simplemente defina Uncaughty reemplace la prueba con la carga útil.


En conclusión:

Tal código es realmente horrible , y nunca debe usarse , pero a veces se usa en ataques XSS, por lo que la moraleja de la historia es no confiar en el filtrado de paréntesis para prevenir XSS. Usar un CSP para prevenir dicho código también sería una buena idea.

Alexander O'Mara
fuente
¿No es esto lo mismo que usar evaly escribir la cadena sin usar los caracteres entre paréntesis?
Zev Spitz
@ZenSpitz Sí, pero evalnormalmente requiere paréntesis, por lo tanto, este truco de evasión de filtro.
Alexander O'Mara
55
Podría decirse \x28y \x29todavía son paréntesis. '\x28' === '('.
Trincot
@trincot, que es una especie de punto que cubrí en la respuesta, este es un enfoque de pensamiento lateral que muestra una razón por la cual esto podría hacerse. Lo dejé más claro en la respuesta.
Alexander O'Mara
Además, quien haya votado para eliminar esto, revise las pautas para eliminar las respuestas .
Alexander O'Mara
1

En ES6, tiene lo que se llama Tagged Template Literals .

Por ejemplo:

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

foo`Tagged Template Literals`;

Idan Dagan
fuente
3
De hecho, esta es la respuesta que publiqué 1,5 años antes que la tuya ... ¿No estás seguro de por qué merece una nueva respuesta?
Trincot
2
porque algunas otras personas que desean implementar lo mismo ahora pueden usar la nueva respuesta.
mehta-rohan
3
@ mehta-rohan, no entiendo ese comentario. Si esto tuviera sentido, ¿por qué no duplicar la misma respuesta una y otra vez? No puedo ver cómo eso sería útil.
Trincot