Puis-je nommer une fonction JavaScript et l'exécuter immédiatement?

90

J'en ai plusieurs:

function addEventsAndStuff() {
  // bla bla
}
addEventsAndStuff();

function sendStuffToServer() {
  // send stuff
  // get HTML in response
  // replace DOM
  // add events:
  addEventsAndStuff();
}

La réajout des événements est nécessaire car le DOM a changé, donc les événements précédemment attachés ont disparu. Puisqu'ils doivent également être attachés au départ (duh), ils ont une bonne fonction d'être DRY.

Il n'y a rien de mal avec cette configuration (ou y en a-t-il?), Mais puis-je l'adoucir un peu? J'aimerais créer leaddEventsAndStuff() fonction et l'appeler immédiatement, donc ça n'a pas l'air si amateur.

Les deux suivants répondent avec une erreur de syntaxe:

function addEventsAndStuff() {
  alert('oele');
}();

(function addEventsAndStuff() {
  alert('oele');
})();

N'importe quels preneurs?

Rudie
la source
duplication possible de la fonction d'exécution automatique
Cristian Sanchez
2
Non, @CristianSanchez.
Máxima Alekz
Imho votre deuxième bloc de code est difficile à lire. Dans le premier bloc de code, il est clair pour chaque lecteur que vous appelez la fonction après init. Les lecteurs ont tendance à ignorer la fermeture et à remplacer les crochets à la fin.
kaiser

Réponses:

123

Il n'y a rien de mal avec l'exemple que vous avez publié dans votre question. L'autre façon de procéder peut paraître étrange, mais:

var addEventsAndStuff;
(addEventsAndStuff = function(){
    // add events, and ... stuff
})();

Il existe deux façons de définir une fonction en JavaScript. Une déclaration de fonction:

function foo(){ ... }

et une expression de fonction, qui est tout moyen de définir une fonction autre que celle ci-dessus:

var foo = function(){};
(function(){})();
var foo = {bar : function(){}};

...etc

Les expressions de fonction peuvent être nommées, mais leur nom n'est pas propagé à la portée contenant. Signifiant que ce code est valide:

(function foo(){
   foo(); // recursion for some reason
}());

mais ce n'est pas:

(function foo(){
    ...
}());
foo(); // foo does not exist

Ainsi, pour nommer votre fonction et l'appeler immédiatement, vous devez définir une variable locale, lui affecter votre fonction en tant qu'expression, puis l'appeler.

Mark Kahn
la source
1
"Les expressions de fonction peuvent être nommées" Attention aux expressions de fonction nommées. Ils devraient fonctionner comme vous le décrivez, mais ils ne le font pas sur IE avant IE9 - vous obtenez deux fonctions et le nom de la fonction fuit. Détails: kangax.github.com/nfe (Ceci est finalement corrigé dans IE9.)
TJ Crowder
@TJ - merci pour la référence. Je ne m'en suis pas rendu compte car je n'utilise jamais IE sauf pour tester les produits finaux :) Je suppose que IE9 n'émule pas ce bogue lors du réglage en mode IE7 / 8? Je pense qu'il utilise toujours le moteur IE9 JS ... Meh
Mark Kahn
Si, comme moi, vous rencontrez des problèmes avec jshint avec cette méthode, vous pouvez utiliser la call()méthode à partir d'ici: stackoverflow.com/a/12359154/1231001
cfx
Cette approche peut sembler utile dans certaines circonstances, mais en plus d'être un peu plus difficile à lire, elle semble contenir pratiquement la même quantité de texte.
Vladimir
16

Il y a un bon raccourci à cela (ne pas avoir besoin de déclarer de variables sauf l'attribution de la fonction):

var func = (function f(a) { console.log(a); return f; })('Blammo')
James Gorrie
la source
Quelle rsera la fonction retournée? Le function rou le var r? Juste curieux. Bonne solution. N'avait pas à être un loc cependant =)
Rudie
Je l'ai renommé pour être plus clair, mais return rcela aurait été le function rcar c'était sa portée. J'ai écrit Scala en permanence pour essayer d'obtenir quelque chose en ligne.
James Gorrie
@JamesGorrie, Brilliant shorthand, cependant, j'ai essayé deux fois dans la console, il semble que func, func (), func () () retournent toutes la déclaration de fonction de f (), mais pouvons-nous afficher 'Blammo' par func () comme prévu s'il vous plaît :)?
mike652638
@ mike652638 Je l'ai exécuté dans ma console et la console enregistre blammo?
James Gorrie
5

Il n'y a rien de mal avec cette configuration (ou y en a-t-il?), Mais puis-je l'adoucir un peu?

Regardez plutôt l'utilisation de la délégation d'événements. C'est là que vous surveillez l'événement sur un conteneur qui ne disparaît pas, puis utilisez event.target(ouevent.srcElement sur IE) pour déterminer où l'événement s'est réellement produit et le gérer correctement.

De cette façon, vous n'attachez le ou les gestionnaires qu'une seule fois, et ils continuent de fonctionner même lorsque vous échangez du contenu.

Voici un exemple de délégation d'événements sans utiliser de bibliothèques d'assistance:

(function() {
  var handlers = {};

  if (document.body.addEventListener) {
    document.body.addEventListener('click', handleBodyClick, false);
  }
  else if (document.body.attachEvent) {
    document.body.attachEvent('onclick', handleBodyClick);
  }
  else {
    document.body.onclick = handleBodyClick;
  }

  handlers.button1 = function() {
    display("Button One clicked");
    return false;
  };
  handlers.button2 = function() {
    display("Button Two clicked");
    return false;
  };
  handlers.outerDiv = function() {
    display("Outer div clicked");
    return false;
  };
  handlers.innerDiv1 = function() {
    display("Inner div 1 clicked, not cancelling event");
  };
  handlers.innerDiv2 = function() {
    display("Inner div 2 clicked, cancelling event");
    return false;
  };

  function handleBodyClick(event) {
    var target, handler;

    event = event || window.event;
    target = event.target || event.srcElement;

    while (target && target !== this) {
      if (target.id) {
        handler = handlers[target.id];
        if (handler) {
          if (handler.call(this, event) === false) {
            if (event.preventDefault) {
              event.preventDefault();
            }
            return false;
          }
        }
      }
      else if (target.tagName === "P") {
        display("You clicked the message '" + target.innerHTML + "'");
      }
      target = target.parentNode;
    }
  }

  function display(msg) {
    var p = document.createElement('p');
    p.innerHTML = msg;
    document.body.appendChild(p);
  }

})();

Exemple en direct

Notez que si vous cliquez sur les messages qui sont ajoutés dynamiquement à la page, votre clic est enregistré et géré même s'il n'y a pas de code pour accrocher les événements sur les nouveaux paragraphes ajoutés. Notez également que vos gestionnaires ne sont que des entrées dans une carte et que vous avez un gestionnaire sur le document.bodyqui s'occupe de toute la répartition. Maintenant, vous enracinez probablement cela dans quelque chose de plus ciblé que document.body, mais vous voyez l'idée. De plus, dans ce qui précède, nous distribuons essentiellement par id, mais vous pouvez faire des correspondances aussi complexes ou simples que vous le souhaitez.

Bibliothèques JavaScript modernes comme jQuery , Prototype , YUI , Closure ou l' un de plusieurs autres devraient offrir des fonctionnalités de délégation d'événements pour lisser les différences de navigateur et gérer proprement les cas de bord. jQuery le fait certainement, avec ses fonctions liveet ses delegatefonctions, qui vous permettent de spécifier des gestionnaires en utilisant une gamme complète de sélecteurs CSS3 (et plus encore).

Par exemple, voici le code équivalent utilisant jQuery (sauf que je suis sûr que jQuery gère les cas de bord, la version brute prête à l'emploi ci-dessus ne le fait pas):

(function($) {

  $("#button1").live('click', function() {
    display("Button One clicked");
    return false;
  });
  $("#button2").live('click', function() {
    display("Button Two clicked");
    return false;
  });
  $("#outerDiv").live('click', function() {
    display("Outer div clicked");
    return false;
  });
  $("#innerDiv1").live('click', function() {
    display("Inner div 1 clicked, not cancelling event");
  });
  $("#innerDiv2").live('click', function() {
    display("Inner div 2 clicked, cancelling event");
    return false;
  });
  $("p").live('click', function() {
    display("You clicked the message '" + this.innerHTML + "'");
  });

  function display(msg) {
    $("<p>").html(msg).appendTo(document.body);
  }

})(jQuery);

Copie en direct

TJ Crowder
la source
Je ne veux pas utiliser Event.target, car ce n'est pas toujours la bonne cible. C'est vrai. Si vous cliquez sur un à l' IMGintérieur DIVet que DIVl'événement a l'événement, ce Event.targetsera le IMGet il n'y a aucun moyen d'obtenir l' DIVobjet correctement. edit Btw I hate .live'event' de jQuery
Rudie
@Rudie: Re event.target: Ce que vous décrivez n'est pas faux, c'est correct. Si vous cliquez sur un à l' imgintérieur divet que vous regardez le div, event.targetest le img(et thisest le div). Jetez un autre coup d'œil à ce qui précède. Vous pouvez attacher un gestionnaire à n'importe quel point de la chaîne entre l'élément sur lequel imgvous avez cliqué (le , par exemple) et l'élément que vous regardez (dans ce qui précède, tout en bas document.body). Re live: Je préfère énormément delegate, la version plus contenue de live. J'ai utilisé liveci-dessus parce que c'était le plus proche de ce que j'avais fait dans l'exemple brut. Best,
TJ Crowder
4

Vous voudrez peut-être créer une fonction d'assistance comme celle-ci:

function defineAndRun(name, func) {
    window[name] = func;
    func();
}

defineAndRun('addEventsAndStuff', function() {
    alert('oele');
});
pimvdb
la source
2
Pourquoi est-ce une mauvaise solution? Parce que les fonctions sont enregistrées dans l'espace de noms global? Qui a voté contre cela?
Rudie
Excellente idée:)) -
:) Máxima Alekz
4

Votre code contient une faute de frappe:

(function addEventsAndStuff() {
  alert('oele');
)/*typo here, should be }*/)();

donc

(function addEventsAndStuff() {
  alert('oele');
 })();

travaux. À votre santé!

[ modifier ] basé sur le commentaire: et cela devrait exécuter et renvoyer la fonction en une seule fois:

var addEventsAndStuff = (
 function(){
  var addeventsandstuff =  function(){
    alert('oele');
  };
  addeventsandstuff();
  return addeventsandstuff;
 }()
);
KooiInc
la source
Les brackets stupides se ressemblent tous !! Merci! Bravo en effet!
Rudie
Ouais ... Alors hein ... En fait ça ne marche pas. La fonction est exécutée immédiatement, oui, mais elle n'est enregistrée nulle part, donc appelez-la à nouveau ->ReferenceError
Rudie
@Rudie: vient de découvrir que KomodoEdit fait enfin tout ce que j'ai toujours voulu qu'Aptana Studio fasse (mais sans cesse interrompu), et en plus de cela, il est hautement configurable, rapide et rempli d'addons utiles. Et il met en évidence les parenthèses correspondantes: vous pouvez même donner à ces parenthèses correspondantes une couleur d'avertissement rouge ferme! Essayez-le, c'est gratuit: activestate.com/komodo-edit
KooiInc
Mec ... (Et évidemment je l'ai tapé dans l'éditeur Web SO, pas dans un éditeur de bureau.)
Rudie
Et c'est beaucoup plus de taper et de se souvenir de quoi et comment taper. J'ai aimé la solution originale (ne fonctionne pas), mais la nouvelle est trop difficile =)
Rudie
2

Encore plus simple avec ES6:

var result = ((a, b) => `${a} ${b}`)('Hello','World')
// result = "Hello World"
var result2 = (a => a*2)(5)
// result2 = 10
var result3 = (concat_two = (a, b) => `${a} ${b}`)('Hello','World')
// result3 = "Hello World"
concat_two("My name", "is Foo")
// "My name is Foo"
Alberto
la source
Alors, comment pourrais-je l'appeler à nouveau? Quel est son nom, pour l'appeler? Vous ne gardez que le résultat, pas la fonction.
Rudie
Cette méthode n'est utilisée dans certains cas que si vous devez l'appeler immédiatement et que vous ne souhaitez pas la réexécuter. Quoi qu'il en soit, je modifierai la réponse pour montrer comment pouvoir la rappeler, qu'en pensez-vous? @Rudie
Alberto
1
Ma question portait sur le nommer et le réexécuter. concat_twofonctionne parfaitement, MAIS il le définit toujours dans la portée globale, donc il ne fonctionnera pas avec "use strict". Ce n'est pas important pour moi, mais c'est un petit inconvénient.
Rudie
1

Si vous souhaitez créer une fonction et l'exécuter immédiatement -

// this will create as well as execute the function a()
(a=function a() {alert("test");})();

// this will execute the function a() i.e. alert("test")
a();
Praveen Lobo
la source
1
Attention, lorsque vous utilisez une fonction nommée comme valeur de droite (comme vous le faites ci-dessus), vous utilisez une expression de fonction nommée qui, bien qu'elle devrait être valide, a malheureusement des problèmes dans diverses implémentations (principalement - quel choc - IE ). Détails: kangax.github.com/nfe
TJ Crowder
0

Essayez de faire comme ça:

var addEventsAndStuff = (function(){
    var func = function(){
        alert('ole!');
    };
    func();
    return func;
})();
Alexandre Ruliov
la source
1
Ceci est proche, mais var foo = (function() {...})();appelle la fonction avant l'affectation. La parenthèse doit inclure l'affectation. (Vous voulez attribuer alors appeler.)
David
C'est aussi beaucoup de taper ET de se souvenir de ce qu'il faut taper. Ensuite, je préfère utiliser mes appels actuels.
Rudie