Appel d'une fonction sans parenthèses


275

On m'a dit aujourd'hui qu'il était possible d'invoquer une fonction sans parenthèses. La seule façon dont je pouvais penser était d'utiliser des fonctions comme applyou call.

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

Mais cela nécessite des parenthèses applyet callnous laisse à la case départ. J'ai également envisagé l'idée de passer la fonction à une sorte de gestionnaire d'événements tel que setTimeout:

setTimeout(f, 500);

Mais alors la question devient "comment invoquer setTimeoutsans parenthèses?"

Alors, quelle est la solution à cette énigme? Comment invoquer une fonction en Javascript sans utiliser de parenthèses?


59
N'essayant pas d'être impoli, mais je dois demander: pourquoi?
jehna1

36
@ jehna1 Parce que je répondais à une question sur la façon dont les fonctions sont invoquées et a été corrigée quand j'ai dit qu'elles exigeaient des parenthèses à la fin.
Mike Cluck

3
Incroyable! Je n'imaginais pas que cette question aurait autant de succès .. J'aurais peut-être dû la poser moi-même :-)
Amit

5
C'est le genre de question qui me brise le cœur. À quoi pourraient bien servir ces abus et cette exploitation? Je veux connaître un cas d'utilisation valide où <insert any solution>est objectivement meilleur ou plus utile que f().
Merci

5
@Aaron: vous dites qu'il n'y a pas de raison valable, mais, comme le souligne la réponse d'Alexander O'Mara , on peut se demander si la suppression des crochets est suffisante pour empêcher l'exécution de code - ou qu'en est-il d'une compétition Javascript obscurcie? Bien sûr, c'est un objectif absurde lorsque vous essayez d'écrire du code maintenable, mais c'est une quesetion amusante.
PJTraill

Réponses:


429

Il existe plusieurs façons d'appeler une fonction sans parenthèses.

Supposons que cette fonction soit définie:

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

Ensuite, voici quelques façons d'appeler greetsans parenthèses:

1. En tant que constructeur

Avec newvous pouvez invoquer une fonction sans parenthèses:

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

De MDN sur l' newoprateur :

Syntaxe

new constructor[([arguments])]

2. Au fur toStringet à mesure de la valueOfmise en œuvre

toStringet valueOfsont des méthodes spéciales: elles sont appelées implicitement lorsqu'une conversion est nécessaire:

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

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

Vous pouvez (ab) utiliser ce modèle pour appeler greetsans parenthèses:

'' + { toString: greet };

Ou avec valueOf:

+{ valueOf: greet };

valueOfet toStringsont en fait appelés à partir de la méthode @@ toPrimitive (depuis ES6), et vous pouvez donc également implémenter cette méthode:

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

2.b Remplacement valueOfdu prototype de fonction

Vous pouvez prendre l'idée précédente pour remplacer la valueOfméthode sur le Functionprototype :

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

Une fois que vous avez fait cela, vous pouvez écrire:

+greet;

Et bien qu'il y ait des parenthèses impliquées sur la ligne, l'invocation de déclenchement réelle n'a pas de parenthèses. Voir plus à ce sujet dans le blog "Appeler des méthodes en JavaScript, sans vraiment les appeler"

3. En tant que générateur

Vous pouvez définir une fonction de générateur (avec *), qui renvoie un itérateur . Vous pouvez l'appeler en utilisant la syntaxe étendue ou avec la for...ofsyntaxe.

Nous avons d'abord besoin d'une variante de générateur de la greetfonction d' origine :

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

Et puis nous l'appelons sans parenthèses en définissant la méthode @@ itérateur :

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

Normalement, les générateurs auraient un yieldmot - clé quelque part, mais il n'est pas nécessaire d'appeler la fonction.

La dernière instruction appelle la fonction, mais cela pourrait également être fait avec la déstructuration :

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

ou une for ... ofconstruction, mais elle a ses propres parenthèses:

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

Notez que vous pouvez également faire ce qui précède avec la greetfonction d' origine , mais cela déclenchera une exception dans le processus, après greet avoir été exécuté (testé sur FF et Chrome). Vous pouvez gérer l'exception avec un try...catchbloc.

4. En tant que Getter

@ jehna1 a une réponse complète à ce sujet, alors donnez-lui du crédit. Voici un moyen d'appeler une fonction sans parenthèses sur la portée globale, en évitant la méthode obsolète__defineGetter__ . Il utilise à la Object.definePropertyplace.

Nous devons créer une variante de la greetfonction d' origine pour cela:

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

Puis:

greet_get;

Remplacez windowpar quel que soit votre objet global.

Vous pouvez appeler la greetfonction d' origine sans laisser de trace sur l'objet global comme ceci:

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

Mais on pourrait dire que nous avons des parenthèses ici (bien qu'ils ne soient pas impliqués dans l'invocation réelle).

5. Comme fonction d'étiquette

Depuis ES6, vous pouvez appeler une fonction en lui passant un modèle littéral avec cette syntaxe:

greet``;

Voir "Littéraux de modèle balisés" .

6. En tant que gestionnaire de proxy

Depuis ES6, vous pouvez définir un proxy :

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

Et puis la lecture de toute valeur de propriété invoquera greet:

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

Il en existe de nombreuses variantes. Un autre exemple:

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

1 in proxy; // triggers greet

7. En tant que vérificateur d'instance

L' instanceofopérateur exécute la @@hasInstanceméthode sur le deuxième opérande, lorsqu'il est défini:

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

1
C'est une approche très intéressante à utiliser valueOf.
Mike Cluck

4
Vous avez oublié ES6:func``;
Ismael Miguel

1
Vous avez utilisé des parenthèses en appelant eval :)
trincot

1
Dans ce cas, la fonction n'est pas exécutée, mais transmise à l'appelant.
trincot

1
@trincot Une autre méthode sera bientôt possible avec l' exploitant du pipeline :'1' |> alert
deniro

224

La façon la plus simple de le faire est avec l' newopérateur:

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

new f;

Bien que ce soit peu orthodoxe et non naturel, cela fonctionne et est parfaitement légal.

L' newopérateur n'a pas besoin de parenthèses si aucun paramètre n'est utilisé.


6
Correction, la façon la plus simple de procéder consiste à ajouter un opérande qui force l'expression de la fonction à évaluer. !faboutit à la même chose, sans instancier une instance de la fonction constructeur (ce qui nécessite de créer un nouveau ce contexte). Il est beaucoup plus performant et est utilisé dans de nombreuses bibliothèques populaires (comme Bootstrap).
THEtheChad

6
@THEtheChad - évaluer une expression n'est pas la même chose que d'exécuter une fonction, mais si vous avez une méthode différente (plus agréable? Plus surprenante?) D'invoquer (provoquer l'exécution) d'une fonction - postez une réponse!
Amit

Le point de préfixer un point d'exclamation, ou tout autre caractère unique opérateur unaire ( +, -, ~) dans ces bibliothèques, est d'enregistrer un octet dans la version réduite de la bibliothèque où la valeur de retour n'est pas nécessaire. (c.-à-d. !function(){}()) La syntaxe auto-appelante habituelle est (function(){})()(malheureusement, la syntaxe function(){}()n'est pas multi-navigateur) Étant donné que la fonction auto-appelante la plus élevée n'a généralement rien à retourner, elle est généralement la cible de cette technique d'économie d'octets
Ultimater

1
@THEtheChad Est-ce vrai? Je viens de l'essayer dans Chrome et je n'obtenais pas l'exécution de la fonction. function foo() { alert("Foo!") };Par exemple: !fooretoursfalse
Glenn 'devalias'

95

Vous pouvez utiliser des getters et setters.

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

Exécutez ce script uniquement avec:

h.ello  // Fires up alert "world"

Éditer:

Nous pouvons même faire des arguments!

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

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

Modifier 2:

Vous pouvez également définir des fonctions globales pouvant être exécutées sans parenthèses:

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

Et avec des arguments:

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

Avertissement:

Comme l'a déclaré @MonkeyZeus: Vous n'utiliserez jamais ce morceau de code en production, quelles que soient vos intentions.


9
Strictement parlant du POV de "Je suis un fou maniant une hache qui a l'honneur de reprendre votre code parce que vous êtes passé à des choses plus grandes et meilleures; pourtant je sais où vous vivez". J'espère vraiment que ce n'est pas normal lol. L'utiliser à l'intérieur d'un plug-in que vous maintenez, acceptable. Écriture de code de logique commerciale, veuillez
patienter pendant que j'affûte

3
@MonkeyZeus haha, oui! Ajout d'une clause de non-responsabilité pour les codeurs du futur qui peuvent trouver cela sur Google
jehna1

En fait, cette exclusion de responsabilité est trop sévère. Il existe des cas d'utilisation légitimes pour "getter en tant qu'appel de fonction". En fait, il est largement utilisé dans une bibliothèque très réussie. (voulez-vous un indice ?? :-)
Amit

1
Notez que __defineGetter__c'est obsolète. Utilisez Object.definePropertyplutôt.
Felix Kling

2
@MonkeyZeus - comme je l'ai explicitement écrit dans le commentaire - ce style d'invocation de fonction est utilisé , intensivement et intentionnellement, dans une bibliothèque publique très réussie comme l'API elle-même, pas en interne.
Amit

23

Voici un exemple pour une situation particulière:

window.onload = funcRef;

Bien que cette déclaration ne soit pas réellement invoquée, elle entraînera une future invocation .

Mais je pense que les zones grises pourraient convenir à des énigmes comme celle-ci :)


6
C'est bien mais ce n'est pas du JavaScript, c'est du DOM.
Amit

6
@Amit, le DOM fait partie de l'environnement JS du navigateur, qui est toujours JavaScript.
zzzzBov

3
@zzzzBov Tous les ECMAScript ne s'exécutent pas dans un navigateur Web. Ou êtes-vous parmi les personnes qui utilisent "JavaScript" pour se référer spécifiquement à la combinaison d'ES avec le DOM HTML, par opposition à Node?
Damian Yerrick

9
@DamianYerrick, je considère le DOM comme une bibliothèque disponible dans certains environnements JS, ce qui ne le rend pas moins JavaScript que le soulignement étant disponible dans certains environnements. C'est toujours du JavaScript, ce n'est tout simplement pas une partie spécifiée dans la spécification ECMAScript.
zzzzBov

3
@zzzzBov, "Bien que le DOM soit souvent accessible à l'aide de JavaScript, il ne fait pas partie du langage JavaScript. Il peut également être consulté par d'autres langues." . Par exemple, FireFox utilise XPIDL et XPCOM pour l'implémentation DOM. Il n'est pas si évident que l'appel de la fonction de rappel spécifiée soit implémenté en JavaScript. Le DOM n'est pas une API comme les autres bibliothèques JavaScript.
trincot

17

Si nous acceptons une approche de réflexion latérale, dans un navigateur, il existe plusieurs API dont nous pouvons abuser pour exécuter du JavaScript arbitraire, y compris l'appel d'une fonction, sans aucun caractère de parenthèse.

1. locationet javascript:protocole:

L'une de ces techniques consiste à abuser du javascript:protocole lors de la locationcession.

Exemple de travail:

location='javascript:alert\x281\x29'

Bien que techniquement \x28 et \x29toujours entre parenthèses une fois le code évalué, le caractère réel (et )n'apparaît pas. Les parenthèses sont échappées dans une chaîne de JavaScript qui est évaluée lors de l'affectation.


2. onerroret eval:

De même, en fonction du navigateur, nous pouvons abuser du global onerror, en le définissant sur eval, et en lançant quelque chose qui se transforme en JavaScript valide. Celui-ci est plus délicat, car les navigateurs ne sont pas cohérents dans ce comportement, mais voici un exemple pour Chrome.

Exemple de travail pour Chrome (pas Firefox, d'autres non testés):

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

Cela fonctionne dans Chrome car throw'test'passera 'Uncaught test'comme premier argument à onerror, ce qui est presque du JavaScript valide. Si nous le faisons à la place, throw';test'cela passera 'Uncaught ;test'. Nous avons maintenant un JavaScript valide! Il suffit de définir Uncaughtet de remplacer test par la charge utile.


En conclusion:

Un tel code est vraiment horrible et ne devrait jamais être utilisé , mais il est parfois utilisé dans les attaques XSS, donc la morale de l'histoire est de ne pas compter sur le filtrage des parenthèses pour empêcher XSS. L'utilisation d'un CSP pour empêcher un tel code serait également une bonne idée.


N'est-ce pas la même chose que d'utiliser evalet d'écrire la chaîne sans réellement utiliser les caractères entre parenthèses.
Zev Spitz

@ZenSpitz Oui, mais evalnécessite normalement des parenthèses, d'où ce piratage d'évasion de filtre.
Alexander O'Mara

5
Sans doute \x28et \x29sont encore entre parenthèses. '\x28' === '('.
trincot

@trincot Ce qui est en quelque sorte le point que j'ai couvert dans la réponse, c'est une approche de pensée latérale qui présente une raison pour laquelle cela pourrait être fait. Je l'ai précisé dans la réponse.
Alexander O'Mara

En outre, quiconque a voté pour la supprimer, veuillez consulter les directives de suppression des réponses .
Alexander O'Mara

1

Dans ES6, vous avez ce qu'on appelle des littéraux de modèle étiquetés .

Par exemple:

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

foo`Tagged Template Literals`;


3
En effet, c'est dans la réponse que j'ai postée 1,5 ans avant la vôtre ... vous ne savez pas pourquoi elle mérite une nouvelle réponse?
trincot

2
parce que d'autres personnes, qui souhaitent mettre en œuvre la même chose en ce moment peuvent utiliser la nouvelle réponse.
mehta-rohan

3
@ mehta-rohan, je ne comprends pas ce commentaire. Si cela avait du sens, alors pourquoi ne pas répéter la même réponse encore et encore? Je ne vois pas en quoi cela serait utile.
trincot
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.