Aufrufen einer Funktion ohne Klammern

275

Mir wurde heute gesagt, dass es möglich ist, eine Funktion ohne Klammern aufzurufen. Ich konnte mir nur vorstellen, Funktionen wie applyoder zu verwenden call.

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

Aber diese erfordern Klammern applyund calllassen uns auf dem ersten Platz. Ich dachte auch über die Idee nach, die Funktion an eine Art Ereignishandler zu übergeben, wie zum Beispiel setTimeout:

setTimeout(f, 500);

Aber dann lautet die Frage: "Wie rufst du setTimeoutohne Klammern auf?"

Was ist die Lösung für dieses Rätsel? Wie können Sie eine Funktion in Javascript aufrufen, ohne Klammern zu verwenden?

Mike Cluck
quelle
59
Ich versuche nicht unhöflich zu sein, aber ich muss fragen: Warum?
Jehna1
36
@ jehna1 Weil ich eine Frage zum Aufrufen von Funktionen beantwortet habe und diese korrigiert wurde, als ich sagte, dass am Ende Klammern erforderlich sind.
Mike Cluck
3
Tolle! Ich hätte nicht gedacht, dass diese Frage so viel Traktion haben würde. Vielleicht hätte ich es mir selbst stellen sollen :-)
Amit
5
Diese Art von Frage bricht mir das Herz. Was könnte ein solcher Missbrauch und eine solche Ausbeutung bringen? Ich möchte einen gültigen Anwendungsfall kennen, der <insert any solution>objektiv besser oder nützlicher ist als f().
Vielen Dank, dass Sie
5
@Aaron: Sie sagen, es gibt keinen gültigen Grund, aber wie die Antwort von Alexander O'Mara zeigt, könnte man sich die Frage überlegen, ob das Entfernen von Klammern ausreicht, um die Codeausführung zu verhindern - oder was ist mit einem verschleierten Javascript-Wettbewerb? Natürlich ist es ein absurdes Ziel, wenn man versucht, wartbaren Code zu schreiben, aber es ist eine amüsante Frage.
PJTraill

Antworten:

428

Es gibt verschiedene Möglichkeiten, eine Funktion ohne Klammern aufzurufen.

Nehmen wir an, Sie haben diese Funktion definiert:

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

Dann folgen hier einige Möglichkeiten, um greetohne Klammern aufzurufen :

1. Als Konstruktor

Mit können newSie eine Funktion ohne Klammern aufrufen:

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

Von MDN auf dem newOprator :

Syntax

new constructor[([arguments])]

2. As toStringoder valueOfImplementierung

toStringund valueOfsind spezielle Methoden: Sie werden implizit aufgerufen, wenn eine Konvertierung erforderlich ist:

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

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

Sie können dieses Muster (ab) verwenden, um greetohne Klammern aufzurufen :

'' + { toString: greet };

Oder mit valueOf:

+{ valueOf: greet };

valueOfund toStringwerden tatsächlich von der @@ toPrimitive- Methode (seit ES6) aufgerufen , sodass Sie diese Methode auch implementieren können :

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

2.b Überschreiben valueOfdes Funktionsprototyps

Sie können die vorherige Idee verwenden, um die valueOfMethode für den FunctionPrototyp zu überschreiben :

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

Sobald Sie das getan haben, können Sie schreiben:

+greet;

Und obwohl es in der Folge Klammern gibt, enthält der eigentliche auslösende Aufruf keine Klammern. Weitere Informationen hierzu finden Sie im Blog "Aufrufen von Methoden in JavaScript, ohne sie wirklich aufzurufen".

3. Als Generator

Sie können eine Generatorfunktion (mit *) definieren, die einen Iterator zurückgibt . Sie können es mit der Spread-Syntax oder mit der for...ofSyntax aufrufen .

Zuerst brauchen wir eine Generatorvariante der ursprünglichen greetFunktion:

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

Und dann rufen wir es ohne Klammern auf, indem wir die Iterator- Methode @@ definieren :

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

Normalerweise haben Generatoren yieldirgendwo ein Schlüsselwort, aber es wird nicht benötigt, damit die Funktion aufgerufen wird.

Die letzte Anweisung ruft die Funktion auf, aber das könnte auch mit Destrukturierung geschehen :

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

oder ein for ... ofKonstrukt, aber es hat eigene Klammern:

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

Beachten Sie, dass Sie können die oben mit dem Original zu tun greetund Funktion, aber es wird eine Ausnahme in den Prozess auslösen, nachdem greet ausgeführt wurde (getestet auf FF und Chrome). Sie können die Ausnahme mit einem try...catchBlock verwalten.

4. Als Getter

@ jehna1 hat eine vollständige Antwort darauf, also gib ihm Anerkennung. Hier ist eine Möglichkeit, eine Funktion ohne Klammern für den globalen Bereich aufzurufen , wobei die veraltete__defineGetter__ Methode vermieden wird. Es wird Object.definePropertystattdessen verwendet.

greetDazu müssen wir eine Variante der ursprünglichen Funktion erstellen :

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

Und dann:

greet_get;

Ersetzen Sie windowdurch was auch immer Ihr globales Objekt ist.

Sie können die ursprüngliche greetFunktion aufrufen, ohne eine Spur für das globale Objekt zu hinterlassen:

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

Man könnte jedoch argumentieren, dass wir hier Klammern haben (obwohl sie nicht am eigentlichen Aufruf beteiligt sind).

5. Als Tag-Funktion

Seit ES6 können Sie eine Funktion aufrufen, die ihr ein Vorlagenliteral mit folgender Syntax übergibt :

greet``;

Siehe "Tagged Template Literals" .

6. Als Proxy-Handler

Seit ES6 können Sie einen Proxy definieren :

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

Wenn Sie dann einen beliebigen Eigenschaftswert lesen, wird Folgendes aufgerufen greet:

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

Es gibt viele Variationen davon. Noch ein Beispiel:

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

1 in proxy; // triggers greet

7. Als Instanzprüfer

Der instanceofOperator führt die @@hasInstanceMethode für den zweiten Operanden aus, wenn er definiert ist:

1 instanceof { [Symbol.hasInstance]: greet } // triggers greet
Trincot
quelle
1
Das ist ein sehr interessanter Ansatz valueOf.
Mike Cluck
4
Sie haben ES6 vergessen:func``;
Ismael Miguel
1
Sie haben Klammern verwendet, indem Sie eval angerufen haben :)
trincot
1
In diesem Fall wird die Funktion nicht ausgeführt, sondern an den Aufrufer übergeben.
Trincot
1
@ Trincot Eine andere Methode wird bald mit Pipeline-Betreiber möglich sein :'1' |> alert
Deniro
224

Der einfachste Weg, dies zu tun, ist mit dem newBediener:

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

new f;

Das ist zwar unorthodox und unnatürlich, funktioniert aber und ist vollkommen legal.

Der newOperator benötigt keine Klammern, wenn keine Parameter verwendet werden.

Amit
quelle
6
Korrektur: Der einfachste Weg, dies zu tun, besteht darin, einen Operanden voranzustellen, der die Auswertung des Funktionsausdrucks erzwingt. !fführt zu demselben Ergebnis, ohne eine Instanz der Konstruktorfunktion zu instanziieren (für die ein neuer Kontext erstellt werden muss). Es ist viel leistungsfähiger und wird in vielen gängigen Bibliotheken (wie Bootstrap) verwendet.
THEtheChad
6
@THEtheChad - Das Auswerten eines Ausdrucks ist nicht dasselbe wie das Ausführen einer Funktion. Wenn Sie jedoch eine andere (schönere? Überraschendere?) Methode zum Aufrufen (Verursachen der Ausführung) einer Funktion haben, geben Sie eine Antwort ein!
Amit
Der Punkt , der das Voranstellen ein Ausrufezeichen oder ein anderes einzelnes Zeichen unärer Operator ( +, -, ~) in solchen Bibliotheken, ist ein Byte in der minimierten Version der Bibliothek zu speichern , wo der Rückgabewert ist nicht erforderlich. (dh !function(){}()) Die übliche selbstaufrufende Syntax ist (function(){})()(leider ist die Syntax function(){}()nicht browserübergreifend). Da die oberste selbstaufrufende Funktion normalerweise nichts zurückzugeben hat, ist sie normalerweise das Ziel dieser Technik zum Speichern von Bytes
Ultimater
1
@THEtheChad Gilt das? Ich habe es gerade in Chrome versucht und keine Funktionsausführung erhalten. function foo() { alert("Foo!") };Zum Beispiel: !fookehrt zurückfalse
Glenn 'devalias'
95

Sie können Getter und Setter verwenden.

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

Führen Sie dieses Skript nur mit:

h.ello  // Fires up alert "world"

Bearbeiten:

Wir können sogar argumentieren!

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

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

Bearbeiten 2:

Sie können auch globale Funktionen definieren, die ohne Klammern ausgeführt werden können:

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

Und mit Argumenten:

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

Haftungsausschluss:

Wie @MonkeyZeus sagte: Niemals dürfen Sie diesen Code in der Produktion verwenden, egal wie gut Ihre Absichten sind.

jehna1
quelle
9
Genau genommen aus der POV von "Ich bin ein Axt schwingender Verrückter, der die Ehre hat, Ihren Code zu übernehmen, weil Sie sich größeren und besseren Dingen zugewandt haben; dennoch weiß ich, wo Sie leben". Ich hoffe wirklich, dass das nicht normal ist lol. Die Verwendung in einem Plug-In, das Sie warten, ist akzeptabel. Schreiben Sie Geschäftslogik-Code, bitte halten Sie, während ich meine Axt schärfe :)
MonkeyZeus
3
@ MonkeyZeus haha, ja! Es wurde ein Haftungsausschluss für Programmierer der Zukunft hinzugefügt, der diesen von Google
jehna1
Eigentlich ist dieser Haftungsausschluss zu hart. Es gibt legitime Anwendungsfälle für "Getter als Funktionsaufruf". Tatsächlich wird es in einer sehr erfolgreichen Bibliothek ausgiebig verwendet. (
Amit
1
Beachten Sie, dass dies __defineGetter__veraltet ist. Verwenden Sie Object.definePropertystattdessen.
Felix Kling
2
@MonkeyZeus - wie ich ausdrücklich in dem Kommentar geschrieben - diese Art des Funktionsaufrufes verwendet wird , umfangreich und absichtlich, in einer sehr erfolgreichen öffentlichen Bibliothek als die API selbst, nicht intern.
Amit
23

Hier ist ein Beispiel für eine bestimmte Situation:

window.onload = funcRef;

Diese Anweisung wird zwar nicht aufgerufen , führt aber zu einem zukünftigen Aufruf .

Aber ich denke, Grauzonen könnten für solche Rätsel in Ordnung sein :)

Jonathan.Brink
quelle
6
Das ist schön, aber das ist kein JavaScript, sondern DOM.
Amit
6
@Amit, das DOM ist Teil der JS-Umgebung des Browsers, die immer noch JavaScript ist.
zzzzBov
3
@zzzzBov Nicht alle ECMAScript werden in einem Webbrowser ausgeführt. Oder gehören Sie zu den Leuten, die "JavaScript" verwenden, um sich speziell auf die Kombination von ES mit dem HTML-DOM im Gegensatz zu Node zu beziehen?
Damian Yerrick
9
@DamianYerrick, ich betrachte das DOM als eine Bibliothek, die in einigen JS-Umgebungen verfügbar ist. Dadurch ist es nicht weniger JavaScript als der Unterstrich, der in einigen Umgebungen verfügbar ist. Es ist immer noch JavaScript, es ist nur kein Teil, der in der ECMAScript-Spezifikation spezifiziert ist.
zzzzBov
3
@zzzzBov, "Obwohl auf das DOM häufig mit JavaScript zugegriffen wird, ist es nicht Teil der JavaScript-Sprache. Es kann auch von anderen Sprachen aufgerufen werden." . Beispielsweise verwendet FireFox XPIDL und XPCOM für die DOM-Implementierung. Es ist nicht so selbstverständlich, dass der Aufruf Ihrer angegebenen Rückruffunktion in JavaScript implementiert ist. Das DOM ist keine API wie andere JavaScript-Bibliotheken.
Trincot
17

Wenn wir einen Ansatz des Querdenkens akzeptieren, gibt es in einem Browser mehrere APIs, die wir missbrauchen können, um beliebiges JavaScript auszuführen, einschließlich des Aufrufs einer Funktion, ohne tatsächliche Klammerzeichen.

1. locationund javascript:Protokoll:

Eine solche Technik besteht darin, das javascript:Protokoll bei der locationZuweisung zu missbrauchen .

Arbeitsbeispiel:

location='javascript:alert\x281\x29'

Obwohl technisch \x28 und \x29immer noch in Klammern, sobald der Code ausgewertet wird, ist der tatsächliche( und das )Zeichen nicht angezeigt. Die Klammern werden in einer JavaScript-Zeichenfolge maskiert, die bei der Zuweisung ausgewertet wird.


2. onerror und eval:

Ebenso können wir je nach Browser das Globale missbrauchen onerror , indem es auf setzen evalund etwas auslösen, das zu gültigem JavaScript führt. Dieser ist schwieriger, da Browser in diesem Verhalten inkonsistent sind, aber hier ist ein Beispiel für Chrome.

Arbeitsbeispiel für Chrome (nicht Firefox, andere ungetestet):

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

Dies funktioniert in Chrome, da throw'test'es 'Uncaught test'als erstes Argument an onerrordas fast gültige JavaScript übergeben wird. Wenn wir es stattdessen tun throw';test', wird es vergehen 'Uncaught ;test'. Jetzt haben wir gültiges JavaScript! Definieren Sie einfach Uncaughtden Test und ersetzen Sie ihn durch die Nutzlast.


Abschließend:

Solcher Code ist wirklich schrecklich und sollte niemals verwendet werden , wird aber manchmal bei XSS-Angriffen verwendet, sodass die Moral der Geschichte nicht darauf beruht, Klammern zu filtern, um XSS zu verhindern. Die Verwendung eines CSP zur Verhinderung eines solchen Codes wäre ebenfalls eine gute Idee.

Alexander O'Mara
quelle
Ist dies nicht dasselbe wie das Verwenden evalund Schreiben der Zeichenfolge, ohne die Klammern zu verwenden?
Zev Spitz
@ZenSpitz Ja, evalerfordert aber normalerweise Klammern, daher dieser Filter-Ausweich-Hack.
Alexander O'Mara
5
Wohl \x28und \x29sind immer noch Klammern. '\x28' === '('.
Trincot
@trincot Dies ist ein Punkt, den ich in der Antwort behandelt habe. Dies ist ein Ansatz des Querdenkens, der einen Grund aufzeigt, aus dem dies geschehen könnte. Ich habe es in der Antwort klarer gemacht.
Alexander O'Mara
Wer auch immer dafür gestimmt hat, dies zu löschen, lesen Sie bitte die Richtlinien zum Löschen von Antworten .
Alexander O'Mara
1

In ES6 haben Sie sogenannte Tagged Template Literals .

Beispielsweise:

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

foo`Tagged Template Literals`;

Idan Dagan
quelle
3
Dies ist in der Tat die Antwort, die ich 1,5 Jahre vor Ihrer gepostet habe ... Sie sind sich nicht sicher, warum sie eine neue Antwort verdient?
Trincot
2
weil einige andere Leute, die das Gleiche jetzt implementieren wollen, die neue Antwort verwenden können.
Mehta-Rohan
3
@ Mehta-Rohan, ich verstehe diesen Kommentar nicht. Wenn dies sinnvoll wäre, warum nicht immer wieder dieselbe Antwort duplizieren? Ich kann nicht sehen, wie das nützlich wäre.
Trincot