Richiamare una funzione senza parentesi


275

Oggi mi è stato detto che è possibile invocare una funzione senza parentesi. L'unico modo in cui potevo pensare era usare funzioni come applyo call.

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

Ma questi richiedono parentesi applye calllasciarci al punto di partenza. Ho anche considerato l'idea di passare la funzione a una sorta di gestore di eventi come setTimeout:

setTimeout(f, 500);

Ma poi la domanda diventa "come si fa a invocare setTimeoutsenza parentesi?"

Qual è la soluzione a questo indovinello? Come si può invocare una funzione in Javascript senza usare le parentesi?


59
Non cercare di essere scortese, ma devo chiedere: perché?
jehna1

36
@ jehna1 Perché stavo rispondendo a una domanda su come le funzioni sono invocate e sono stato corretto quando ho detto che alla fine erano necessarie parentesi.
Mike Cluck,

3
Sorprendente! Non immaginavo che questa domanda avrebbe avuto così tanta trazione .. Forse avrei dovuto chiedermelo anch'io :-)
Tra il

5
Questo è il tipo di domanda che mi spezza il cuore. Che bene potrebbe derivare da un simile abuso e sfruttamento? Voglio conoscere un caso d'uso valido in cui <insert any solution>sia oggettivamente migliore o più utile di f().
Grazie

5
@Aaron: dici che non esiste un motivo valido, ma, come sottolinea la risposta di Alexander O'Mara , si potrebbe considerare la domanda se la rimozione delle parentesi è sufficiente per impedire l'esecuzione del codice - o che dire di una concorrenza Javascript offuscata? Ovviamente è un obiettivo assurdo quando si cerca di scrivere codice gestibile, ma è una domanda divertente.
PJTraill

Risposte:


429

Esistono diversi modi per chiamare una funzione senza parentesi.

Supponiamo che tu abbia questa funzione definita:

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

Quindi ecco alcuni modi per chiamare greetsenza parentesi:

1. Come costruttore

Con newte puoi invocare una funzione senza parentesi:

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

Da MDN newsull'opratore :

Sintassi

new constructor[([arguments])]

2. Come toStringo valueOfimplementazione

toStringe valueOfsono metodi speciali: vengono chiamati implicitamente quando è necessaria una conversione:

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

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

È possibile (ab) utilizzare questo modello per chiamare greetsenza parentesi:

'' + { toString: greet };

O con valueOf:

+{ valueOf: greet };

valueOfe toStringsono infatti chiamati dal metodo @@ toPrimitive (dal momento che ES6), e quindi è anche possibile implementare quel metodo:

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

2.b Sostituzione valueOfdel prototipo della funzione

Potresti prendere l'idea precedente per sovrascrivere il valueOfmetodo sul Functionprototipo :

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

Dopo averlo fatto, puoi scrivere:

+greet;

E sebbene ci siano parentesi coinvolte lungo la linea, l'invocazione effettiva non ha parentesi. Scopri di più su questo nel blog "Metodi di chiamata in JavaScript, senza davvero chiamarli"

3. Come generatore

È possibile definire una funzione del generatore (con *), che restituisce un iteratore . Puoi chiamarlo usando la sintassi diffusa o con la for...ofsintassi.

Per prima cosa abbiamo bisogno di una variante del generatore della greetfunzione originale :

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

E quindi lo chiamiamo senza parentesi definendo il metodo @@ iterator :

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

Normalmente i generatori avrebbero una yieldparola chiave da qualche parte, ma non è necessario che la funzione venga chiamata.

L'ultima istruzione richiama la funzione, ma ciò potrebbe essere fatto anche con la destrutturazione :

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

o un for ... ofcostrutto, ma ha le sue parentesi:

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

Nota che puoi fare quanto sopra anche con la greetfunzione originale , ma innescherà un'eccezione nel processo, dopo che greet è stato eseguito (testato su FF e Chrome). È possibile gestire l'eccezione con un try...catchblocco.

4. Come Getter

@ jehna1 ha una risposta completa su questo, quindi dagli credito. Ecco un modo per chiamare una funzione tra parentesi sull'ambito globale, evitando il metodo deprecato__defineGetter__ . Usa Object.definePropertyinvece.

Per questo dobbiamo creare una variante della greetfunzione originale :

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

E poi:

greet_get;

Sostituisci windowcon qualunque sia il tuo oggetto globale.

È possibile chiamare la greetfunzione originale senza lasciare una traccia sull'oggetto globale in questo modo:

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

Ma si potrebbe sostenere che qui abbiamo parentesi (anche se non sono coinvolte nell'effettiva invocazione).

5. Come funzione tag

Da ES6 puoi chiamare una funzione passandole letteralmente come template con questa sintassi:

greet``;

Vedere "Letterali modello con tag" .

6. Come gestore proxy

Da ES6, è possibile definire un proxy :

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

E quindi la lettura di qualsiasi valore di proprietà invocherà greet:

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

Ci sono molte varianti di questo. Un altro esempio:

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

1 in proxy; // triggers greet

7. Come controllo istanza

L' instanceofoperatore esegue il @@hasInstancemetodo sul secondo operando, quando definito:

1 instanceof { [Symbol.hasInstance]: greet } // triggers greet

1
Questo è un approccio molto interessante usando valueOf.
Mike Cluck,

4
Hai dimenticato ES6:func``;
Ismael Miguel,

1
Hai usato parentesi chiamando eval :)
trincot

1
In tal caso la funzione non viene eseguita, ma passata al chiamante.
trincot

1
@trincot Un altro metodo sarà presto possibile con l' operatore della pipeline :'1' |> alert
deniro

224

Il modo più semplice per farlo è con l' newoperatore:

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

new f;

Sebbene sia poco ortodosso e innaturale, funziona ed è perfettamente legale.

L' newoperatore non richiede parentesi se non vengono utilizzati parametri.


6
Correzione, il modo più semplice per farlo è anteporre un operando che forza l'espressione della funzione da valutare. !frisulta nella stessa cosa, senza istanziare un'istanza della funzione di costruzione (che richiede la creazione di un nuovo contesto). È molto più performante ed è usato in molte librerie popolari (come Bootstrap).
THEtheChad era il

6
@THEtheChad - valutare un'espressione non equivale a eseguire una funzione, ma se hai un metodo diverso (più bello? Più sorprendente?) Di invocare (causando l'esecuzione) di una funzione - pubblica una risposta!
Entro il

Il punto di anteponendo un punto esclamativo, o qualsiasi altro singolo carattere operatore unario ( +, -, ~) in tali librerie, è quello di salvare un byte nella versione ridotta della libreria in cui non è necessario il valore di ritorno. (vale a dire !function(){}()) La solita sintassi di auto-chiamata è (function(){})()(purtroppo la sintassi function(){}()non è cross-browser) Poiché la funzione di auto-chiamata più in alto di solito non ha nulla da restituire, di solito è l'obiettivo di questa tecnica di risparmio di byte
Ultimater

1
@THEtheChad È vero? L'ho appena provato in Chrome e non è stata eseguita l'esecuzione della funzione. function foo() { alert("Foo!") };Ad esempio: !fooritornifalse
Glenn 'devalias'

95

Puoi usare getter e setter.

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

Esegui questo script solo con:

h.ello  // Fires up alert "world"

Modificare:

Possiamo persino fare argomenti!

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

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

Modifica 2:

È inoltre possibile definire funzioni globali che possono essere eseguite senza parentesi:

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

E con argomenti:

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

Disclaimer:

Come affermato da @MonkeyZeus: non userai mai questo codice nella produzione, non importa quanto siano buone le tue intenzioni.


9
A rigor di termini dal punto di vista di "Sono un'ascia che maneggia un matto che ha l'onore di prendere il tuo codice perché sei passato a cose più grandi e migliori, eppure so dove vivi". Spero davvero che non sia normale lol. Usandolo all'interno di un plug-in che mantieni, accettabile. Scrivendo il codice della logica aziendale, tieni premuto mentre
affilo la

3
@MonkeyZeus haha, si! Aggiunto un disclaimer per i programmatori del futuro che può trovare questo da Google
jehna1

In realtà questo disclaimer è troppo duro. Esistono casi d'uso legittimi per "getter come chiamata di funzione". In effetti è ampiamente utilizzato in una libreria di grande successo. (vuoi un suggerimento ?? :-)
Amit

1
Si noti che __defineGetter__è obsoleto. Usa Object.definePropertyinvece.
Felix Kling,

2
@MonkeyZeus - come ho scritto esplicitamente nel commento - questo stile di invocazione di funzioni viene utilizzato , ampiamente e intenzionalmente, in una biblioteca pubblica di grande successo come l'API stessa, non internamente.
Entro il

23

Ecco un esempio per una situazione particolare:

window.onload = funcRef;

Sebbene tale affermazione non stia effettivamente invocando, ma porterà a una futura invocazione .

Ma, immagino che le aree grigie potrebbero essere ok per indovinelli come questo :)


6
È carino ma non è JavaScript, è DOM.
Entro l'

6
@Amit, il DOM fa parte dell'ambiente JS del browser, che è ancora JavaScript.
zzzzBov

3
@zzzzBov Non tutti gli ECMAScript vengono eseguiti in un browser web. Oppure sei tra le persone che usano "JavaScript" per riferirsi in modo specifico alla combinazione di ES con il DOM HTML, al contrario di Node?
Damian Yerrick,

9
@DamianYerrick, considero il DOM una libreria disponibile in alcuni ambienti JS, il che non lo rende meno JavaScript del carattere di sottolineatura disponibile in alcuni ambienti. È ancora JavaScript, non è solo una parte specificata nelle specifiche ECMAScript.
zzzzBov,

3
@zzzzBov, "Sebbene si acceda spesso al DOM tramite JavaScript, non fa parte del linguaggio JavaScript. È possibile accedervi anche da altre lingue." . Ad esempio, FireFox utilizza XPIDL e XPCOM per l'implementazione DOM. Non è così evidente che la chiamata della funzione di callback specificata sia implementata in JavaScript. Il DOM non è un'API come altre librerie JavaScript.
trincot

17

Se accettiamo un approccio di pensiero laterale, in un browser ci sono diverse API che possiamo abusare per eseguire JavaScript arbitrario, inclusa la chiamata a una funzione, senza caratteri di parentesi effettivi.

1. locatione javascript:protocollo:

Una di queste tecniche è quella di abusare del javascript:protocollo locationsull'assegnazione.

Esempio di lavoro:

location='javascript:alert\x281\x29'

Sebbene tecnicamente \x28 e \x29siano ancora tra parentesi una volta valutato il codice, l'effettivo (e il )carattere non vengono visualizzati. Le parentesi sono sfuggite a una stringa di JavaScript che viene valutata al momento dell'assegnazione.


2. onerrore eval:

Allo stesso modo, a seconda del browser possiamo abusare del globale onerror, impostandolo su evale lanciando qualcosa che si restringerà a JavaScript valido. Questo è più complicato, perché i browser non sono coerenti con questo comportamento, ma ecco un esempio per Chrome.

Esempio funzionante per Chrome (non Firefox, altri non testati):

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

Questo funziona in Chrome perché throw'test'passerà 'Uncaught test'come primo argomento a onerror, che è JavaScript quasi valido. Se invece lo facciamo throw';test', passerà 'Uncaught ;test'. Ora abbiamo JavaScript valido! Basta definire Uncaughte sostituire il test con il payload.


In conclusione:

Tale codice è davvero terribile e non dovrebbe mai essere usato , ma a volte viene utilizzato negli attacchi XSS, quindi la morale della storia è non fare affidamento sul filtraggio delle parentesi per impedire XSS. Anche usare un CSP per prevenire tale codice sarebbe una buona idea.


Non è lo stesso che usare evale scrivere la stringa senza effettivamente usare i caratteri tra parentesi.
Zev Spitz,

@ZenSpitz Sì, ma evalnormalmente richiede parentesi, quindi questo hack di evasione del filtro.
Alexander O'Mara,

5
Probabilmente \x28e \x29sono ancora tra parentesi. '\x28' === '('.
trincot

@trincot Che è un po 'il punto che ho trattato nella risposta, questo è un approccio di pensiero laterale che mette in mostra un motivo per cui ciò potrebbe essere fatto. L'ho reso più chiaro nella risposta.
Alexander O'Mara,

Inoltre, chiunque abbia votato per eliminarlo, consulta le linee guida per l'eliminazione delle risposte .
Alexander O'Mara,

1

In ES6, hai quello che viene chiamato Letterature dei modelli con tag .

Per esempio:

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

foo`Tagged Template Literals`;


3
In effetti, questo è nella risposta che ho pubblicato 1,5 anni prima del tuo ... non sei sicuro del motivo per cui merita una nuova risposta?
trincot

2
perché alcune altre persone, che vogliono implementare la stessa cosa in questo momento, possono usare la nuova risposta.
Mehta-Rohan,

3
@ mehta-rohan, non capisco quel commento. Se questo avesse senso, allora perché non duplicare la stessa risposta più e più volte guadagno? Non riesco a vedere come sarebbe utile.
trincot
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.