¿Puedo nombrar una función de JavaScript y ejecutarla inmediatamente?

90

Tengo bastantes de estos:

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

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

Es necesario volver a agregar los eventos porque el DOM ha cambiado, por lo que los eventos adjuntos anteriormente se han ido. Como también tienen que estar conectados inicialmente (duh), están en una buena función para estar SECOS.

No hay nada de malo en esta configuración (¿o no?), Pero ¿puedo suavizarlo un poco? Me gustaría crear la addEventsAndStuff()función y llamarla inmediatamente, para que no parezca tan amateur.

Ambos siguientes responden con un error de sintaxis:

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

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

¿Ningún arrendatario?

Rudie
fuente
posible duplicado de la función de ejecución automática
Cristian Sanchez
2
No, @CristianSanchez.
Máxima Alekz
En mi humilde opinión, su segundo bloque de código es difícil de leer. En el primer bloque de código, está claro para todos los lectores que usted llama a la función después de init. Los lectores tienden a ignorar los corchetes de cierre y la lectura excesiva al final.
Kaiser

Respuestas:

123

No hay nada de malo en el ejemplo que publicó en su pregunta. La otra forma de hacerlo puede parecer extraña, pero:

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

Hay dos formas de definir una función en JavaScript. Una declaración de función:

function foo(){ ... }

y una expresión de función, que es cualquier forma de definir una función distinta a la anterior:

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

... etc

Se pueden nombrar expresiones de función, pero su nombre no se propaga al ámbito contenedor. Lo que significa que este código es válido:

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

pero esto no es:

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

Entonces, para nombrar su función e inmediatamente llamarla, necesita definir una variable local, asignarle su función como una expresión y luego llamarla.

Mark Kahn
fuente
1
"Las expresiones de función se pueden nombrar" Tenga cuidado con las expresiones de función con nombre. Ellos deben trabajar como usted describe, pero no lo hacen en IE antes de IE9 - se obtiene dos funciones, y las fugas nombre de función. Detalles: kangax.github.com/nfe (Esto finalmente se solucionó en IE9.)
TJ Crowder
@TJ - gracias por la referencia. No me di cuenta de eso, ya que nunca uso IE excepto para probar productos finales :) ¿Estoy asumiendo que IE9 no emula este error cuando lo configuro en el modo IE7 / 8? Creo que todavía usa el motor IE9 JS ... Meh
Mark Kahn
Si, como yo, tiene problemas con jshint con este método, puede usar el call()método desde aquí: stackoverflow.com/a/12359154/1231001
cfx
Este enfoque puede parecer útil en determinadas circunstancias, pero además de ser un poco más difícil de leer, parece tener prácticamente la misma cantidad de texto.
Vladimir
16

Hay una buena abreviatura para esto (no es necesario declarar ninguna variable que excluya la asignación de la función):

var func = (function f(a) { console.log(a); return f; })('Blammo')
James Gorrie
fuente
¿Cuál rdevolverá la función? ¿El function ro el var r? Sólo curioso. Buena solución. Aunque no tenía que ser una locomotora =)
Rudie
Lo renombré para que sea más claro, pero return rhabría sido el function rya que ese era su alcance. He estado escribiendo Scala permanentemente tratando de poner algo en línea.
James Gorrie
@JamesGorrie, abreviatura brillante, sin embargo, lo intenté dos veces en la consola, parece func, func (), func () () todas devuelven la declaración de función de f (), pero podemos generar 'Blammo' por func () como se esperaba, por favor :)?
mike652638
@ mike652638 Lo he ejecutado en mi consola y la consola registra blammo?
James Gorrie
5

No hay nada de malo en esta configuración (¿o no?), Pero ¿puedo suavizarlo un poco?

En su lugar, considere usar la delegación de eventos. Ahí es donde realmente observa el evento en un contenedor que no desaparece, y luego usa event.target(o event.srcElementen IE) para averiguar dónde ocurrió realmente el evento y manejarlo correctamente.

De esa manera, solo adjunta los controladores una vez y seguirán funcionando incluso cuando intercambie contenido.

A continuación, se muestra un ejemplo de delegación de eventos sin utilizar ninguna biblioteca auxiliar:

(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);
  }

})();

Ejemplo vivo

Tenga en cuenta que si hace clic en los mensajes que se agregan dinámicamente a la página, su clic se registra y se maneja a pesar de que no hay código para enganchar eventos en los nuevos párrafos que se agregan. También tenga en cuenta que sus controladores son solo entradas en un mapa, y tiene un controlador en el document.bodyque realiza todo el envío. Ahora, probablemente arraigue esto en algo más específico que document.body, pero ya entiende la idea. Además, en lo anterior básicamente estamos enviando por id, pero puede hacer una coincidencia tan compleja o simple como desee.

Las bibliotecas de JavaScript modernas como jQuery , Prototype , YUI , Closure o cualquiera de varias otras deberían ofrecer funciones de delegación de eventos para suavizar las diferencias del navegador y manejar los casos extremos de manera limpia. jQuery ciertamente lo hace, con sus funciones livey delegate, que le permiten especificar controladores usando una gama completa de selectores CSS3 (y más).

Por ejemplo, aquí está el código equivalente usando jQuery (excepto que estoy seguro de que jQuery maneja casos extremos que la versión sin procesar de arriba no lo hace):

(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);

Copia en vivo

TJ Crowder
fuente
No quiero usarlo Event.target, porque ese no siempre es el objetivo correcto. Así es. Si hace clic en un IMGdentro de a DIVy DIVtiene el evento, Event.targetserá el IMGy no hay forma de obtener el DIVobjeto correctamente. editar Por cierto, odio el .live'evento' de jQuery
Rudie
@Rudie: Re event.target: Lo que describe no está mal, es correcto. Si hace clic en un imgdentro de a divy está viendo el div, event.targetes el img(y thises el div). Eche otro vistazo a lo anterior. Puede adjuntar un controlador en cualquier punto de la cadena entre el elemento en el que se hizo clic (el img, por ejemplo) y el elemento que está viendo (en el de arriba, hasta abajo document.body). Re live: Prefiero mucho delegate, la versión más contenida de live. Usé livearriba porque era lo más cercano a lo que había hecho en el ejemplo en bruto. Mejor,
TJ Crowder
4

Es posible que desee crear una función auxiliar como esta:

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

defineAndRun('addEventsAndStuff', function() {
    alert('oele');
});
pimvdb
fuente
2
¿Por qué es esta una mala solución? ¿Porque las funciones están registradas en el espacio de nombres global? ¿Quién votó en contra de esto?
Rudie
Gran idea
:)
4

Su código contiene un error tipográfico:

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

entonces

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

trabajos. ¡Salud!

[ editar ] basado en el comentario: y esto debería ejecutar y devolver la función de una sola vez:

var addEventsAndStuff = (
 function(){
  var addeventsandstuff =  function(){
    alert('oele');
  };
  addeventsandstuff();
  return addeventsandstuff;
 }()
);
KooiInc
fuente
¡¡Los estúpidos paréntesis se parecen todos !! ¡Gracias! ¡Salud de verdad!
Rudie
Sí ... Entonces eh ... En realidad, eso no funciona. La función se ejecuta de inmediato, sí, pero no está registrada en ninguna parte, por lo que se vuelve a llamar ->ReferenceError
Rudie
@Rudie: acabo de descubrir que KomodoEdit finalmente hace todo lo que siempre quise que hiciera Aptana Studio (pero se rompió todo el tiempo), y además de eso, es altamente configurable, rápido y está repleto de complementos útiles. Y destaca los soportes a juego: ¡incluso puede darles un color rojo firme de advertencia a esos soportes a juego! Pruébelo
KooiInc
Amigo ... (Y obviamente lo escribí en el editor web SO, no en un editor de escritorio)
Rudie
Y eso es mucho escribir y recordar qué y cómo escribir. Me gustó la solución original (que no funciona), pero la nueva es demasiado difícil =)
Rudie
2

Aún más simple con 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
fuente
Entonces, ¿cómo lo llamaría de nuevo? ¿Cómo se llama, para llamarlo? Solo conserva el resultado, no la función.
Rudie
Este método se usa en algunos casos solo si necesita llamarlo inmediatamente y no desea volver a ejecutarlo. De todos modos editaré la respuesta para mostrar cómo poder volver a llamarlo, ¿qué te parece? @Rudie
Alberto
1
Mi pregunta fue sobre nombrarlo y volver a ejecutarlo. concat_twofunciona perfectamente, PERO siempre lo define en el ámbito global, por lo que no funcionará con "use strict". Eso no es importante para mí, pero es un pequeño inconveniente.
Rudie
1

Si desea crear una función y ejecutarla inmediatamente:

// 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
fuente
1
Tenga cuidado, cuando usa una función nombrada como valor de la derecha (como lo hace arriba), está usando una expresión de función nombrada que, si bien debería ser válida, desafortunadamente tiene problemas en varias implementaciones (principalmente - quelle shock - IE ). Detalles: kangax.github.com/nfe
TJ Crowder
0

Intenta hacer así:

var addEventsAndStuff = (function(){
    var func = function(){
        alert('ole!');
    };
    func();
    return func;
})();
Alexander Ruliov
fuente
1
Esto está cerca, pero var foo = (function() {...})();llama a la función antes de la asignación. El paréntesis debe incluir la tarea. (Desea asignar y luego llamar.)
David
Eso es también mucho escribir Y recordar qué escribir. Entonces prefiero usar mis llamadas actuales.
Rudie