Czy mogę nazwać funkcję JavaScript i wykonać ją natychmiast?

90

Mam kilka z nich:

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

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

Ponowne dodanie wydarzeń jest konieczne, ponieważ DOM się zmienił, więc wcześniej dołączone zdarzenia zniknęły. Ponieważ muszą być również przymocowane na początku (duh), są w fajnej funkcji, aby być SUCHE.

Nie ma nic złego w tej konfiguracji (czy jest?), Ale czy mogę trochę wygładzić? Chciałbym stworzyć addEventsAndStuff()funkcję i od razu ją nazwać, żeby nie wyglądała tak amatorsko.

Oba poniższe odpowiedzi odpowiadają błędem składni:

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

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

Jacyś chętni?

Rudie
źródło
możliwy duplikat samowykonującej się funkcji Javascript
Cristian Sanchez
2
Nie, @CristianSanchez.
Máxima Alekz
Imho twój drugi blok kodu jest trudny do odczytania. W pierwszym bloku kodu dla każdego czytelnika jest jasne, że wywołujesz funkcję po init. Czytelnicy mają tendencję do ignorowania zamykających i zastępujących nawiasy na końcu.
kaiser

Odpowiedzi:

123

Nie ma nic złego w przykładzie, który zamieściłeś w swoim pytaniu. Inna metoda może wyglądać dziwnie, ale:

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

Istnieją dwa sposoby definiowania funkcji w JavaScript. Deklaracja funkcji:

function foo(){ ... }

oraz wyrażenie funkcyjne, które jest dowolnym sposobem zdefiniowania funkcji innej niż powyższe:

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

...itp

wyrażeniom funkcyjnym można nadawać nazwy, ale ich nazwa nie jest propagowana do zakresu zawierającego. Znaczenie tego kodu jest ważne:

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

ale to nie jest:

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

Aby więc nazwać swoją funkcję i natychmiast ją wywołać, musisz zdefiniować zmienną lokalną, przypisać jej funkcję jako wyrażenie, a następnie ją wywołać.

Mark Kahn
źródło
1
„Wyrażenia funkcyjne można nazwać„ Uważaj na nazwane wyrażenia funkcyjne. Oni powinni pracować jak opisujesz, ale nie na IE przed IE9 - masz dwie funkcje, a przecieki nazwa funkcji. Szczegóły: kangax.github.com/nfe (zostało to ostatecznie naprawione w IE9).
TJ Crowder,
@TJ - dzięki za odniesienie. Nie zdawałem sobie z tego sprawy, ponieważ nigdy nie używam IE z wyjątkiem testowania produktów końcowych :) Zakładam, że IE9 nie emuluje tego błędu, ustawiając go w trybie IE7 / 8? Myślę, że nadal używa silnika IE9 JS ... Meh
Mark Kahn
Jeśli, tak jak ja, napotkasz problemy z jshint z tą metodą, możesz użyć call()metody z tego miejsca: stackoverflow.com/a/12359154/1231001
cfx
To podejście może wydawać się przydatne w pewnych okolicznościach, ale oprócz tego, że jest trochę trudniejsze do odczytania, wydaje się, że zawiera praktycznie taką samą ilość tekstu.
Vladimir
16

Jest na to dobry skrót (nie trzeba deklarować żadnych zmiennych poza przypisaniem funkcji):

var func = (function f(a) { console.log(a); return f; })('Blammo')
James Gorrie
źródło
Która rfunkcja zwróci? Czy function rlub var r? Po prostu ciekawy. Dobre rozwiązanie. Nie musiał to być jednak jeden loc =)
Rudie
Zmieniłem nazwę, aby była bardziej przejrzysta, ale return rbyłby taki, function rponieważ był to jego zakres. Pisałem Scalę tak nieustannie, próbując znaleźć coś na linii.
James Gorrie
@JamesGorrie, genialny skrót, jednak próbowałem dwa razy w konsoli, wygląda na to, że func, func (), func () () wszystkie zwracają deklarację funkcji f (), ale czy możemy wyświetlić 'Blammo' przez func () zgodnie z oczekiwaniami, proszę :)?
mike652638
@ mike652638 Uruchomiłem to w mojej konsoli i konsola rejestruje blammo?
James Gorrie,
5

Nie ma nic złego w tej konfiguracji (czy jest?), Ale czy mogę trochę wygładzić?

Zamiast tego spójrz na użycie delegowania zdarzeń. W tym miejscu faktycznie obserwujesz zdarzenie w kontenerze, który nie znika, a następnie używasz event.target(lub event.srcElementw IE), aby dowiedzieć się, gdzie zdarzenie faktycznie miało miejsce i poprawnie je obsłużyć.

W ten sposób dołączasz moduły obsługi tylko raz, a one po prostu działają nawet po wymianie treści.

Oto przykład delegowania zdarzeń bez użycia bibliotek pomocniczych:

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

})();

Przykład na żywo

Zwróć uwagę, że jeśli klikniesz wiadomości, które są dynamicznie dodawane do strony, kliknięcie zostanie zarejestrowane i obsługiwane, nawet jeśli nie ma kodu, który przechwyciłby zdarzenia na dodawanych nowych akapitach. Zwróć również uwagę, że twoje programy obsługi są tylko wpisami na mapie, a masz jeden moduł obsługujący, document.bodyktóry wykonuje całą wysyłkę. Prawdopodobnie zakorzenisz to w czymś bardziej ukierunkowanym niż document.body, ale masz pomysł. Ponadto w powyższym zasadniczo wysyłamy id, ale możesz dopasować tak złożone lub proste, jak chcesz.

Nowoczesne biblioteki JavaScript, takie jak jQuery , Prototype , YUI , Closure lub dowolne inne, powinny oferować funkcje delegowania zdarzeń, aby wygładzić różnice w przeglądarkach i czysto obsługiwać skrajne przypadki. jQuery z pewnością to robi, zarówno z jego funkcjami, jak livei delegate, które pozwalają określić moduły obsługi przy użyciu pełnego zakresu selektorów CSS3 (a także niektórych).

Na przykład, oto równoważny kod używający jQuery (z wyjątkiem tego, że jestem pewien, że jQuery obsługuje przypadki krawędziowe, które nie są dostępne w surowej wersji powyżej):

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

Kopia na żywo

TJ Crowder
źródło
Nie chcę używać Event.target, ponieważ to nie zawsze jest właściwy cel. Zgadza się. Jeśli klikniesz IMGwewnątrz a, DIVa DIVma zdarzenie, Event.targetzostanie wyświetlone IMGi nie ma sposobu, aby DIVładnie uzyskać obiekt. edytuj Btw I hate jQuery's .live'event'
Rudie
@Rudie: Re event.target: To, co opisujesz, nie jest złe, jest poprawne. Jeśli klikniesz imgwewnątrz a divi oglądasz div, event.targetto jest img(i thisjest div). Spójrz ponownie na powyższe. Możesz dołączyć moduł obsługi w dowolnym punkcie łańcucha między klikniętym elementem ( imgna przykład) a elementem, który obserwujesz (powyżej, na samym dole document.body). Re live: Zdecydowanie wolę delegate, bardziej zamkniętą wersję live. Użyłem livepowyżej, ponieważ było to najbliższe temu, co zrobiłem w surowym przykładzie. Pozdrawiam
TJ Crowder
4

Możesz chcieć utworzyć taką funkcję pomocniczą:

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

defineAndRun('addEventsAndStuff', function() {
    alert('oele');
});
pimvdb
źródło
2
Dlaczego jest to złe rozwiązanie? Ponieważ funkcje są zarejestrowane w globalnej przestrzeni nazw? Kto za tym głosował?
Rudie
Świetny pomysł
:)
4

Twój kod zawiera literówkę:

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

więc

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

Pracuje. Twoje zdrowie!

[ edytuj ] na podstawie komentarza: a to powinno uruchomić i zwrócić funkcję za jednym razem:

var addEventsAndStuff = (
 function(){
  var addeventsandstuff =  function(){
    alert('oele');
  };
  addeventsandstuff();
  return addeventsandstuff;
 }()
);
KooiInc
źródło
Wszystkie głupie nawiasy wyglądają podobnie! Dzięki! Doprawdy!
Rudie
Tak ... Więc eh ... Właściwie to nie działa. Funkcja jest wykonywana natychmiastowo, tak, ale nie jest nigdzie zarejestrowana, więc wywołuję ją ponownie ->ReferenceError
Rudie
@Rudie: właśnie odkryłem, że KomodoEdit w końcu robi wszystko, co zawsze chciałem zrobić Aptana Studio (ale cały czas się zepsuło), a do tego jest wysoce konfigurowalny, szybki i pełen przydatnych dodatków. I podkreśla pasujące nawiasy: możesz nawet nadać tym pasującym nawiasom mocny czerwony kolor ostrzegawczy! Spróbuj, to nic nie kosztuje: activestate.com/komodo-edit
KooiInc
Koleś ... (I oczywiście wpisałem to w edytorze internetowym SO, a nie w edytorze desktopowym.)
Rudie
A to dużo dodatkowego pisania i pamiętania, co i jak pisać. Podobało mi się oryginalne (nie działające) rozwiązanie, ale nowe jest zbyt trudne =)
Rudie
2

Jeszcze prostsze z 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
źródło
Więc jak mam to ponownie nazwać? Jak to się nazywa? Zachowujesz tylko wynik, a nie funkcję.
Rudie
Ta metoda jest używana w niektórych przypadkach tylko wtedy, gdy musisz wywołać ją natychmiast i nie chcesz jej ponownie uruchamiać. W każdym razie zredaguję odpowiedź, aby pokazać, jak mogę ją ponownie wywołać, co o tym myślisz? @Rudie
Alberto
1
Moje pytanie dotyczyło nazwania go i ponownego uruchomienia. concat_twodziała idealnie, ALE zawsze definiuje to w zakresie globalnym, więc nie będzie działać "use strict". To nie jest dla mnie ważne, ale to mały minus.
Rudie
1

Jeśli chcesz utworzyć funkcję i wykonać ją natychmiast -

// 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
źródło
1
Uważaj, kiedy używasz nazwanej funkcji jako wartości po prawej stronie (tak jak powyżej), używasz nazwanego wyrażenia funkcji, które chociaż powinno być poprawne, niestety ma problemy w różnych implementacjach (głównie - quelle shock - IE ). Szczegóły: kangax.github.com/nfe
TJ Crowder
0

Spróbuj zrobić tak:

var addEventsAndStuff = (function(){
    var func = function(){
        alert('ole!');
    };
    func();
    return func;
})();
Alexander Ruliov
źródło
1
To jest blisko, ale var foo = (function() {...})();wywołuje funkcję przed przypisaniem. Nawias musi zawierać przypisanie. (Chcesz przydzielić, a następnie zadzwoń.)
David
To także dużo pisania ORAZ pamiętania, co wpisać. W takim razie wolałbym po prostu skorzystać z moich bieżących połączeń.
Rudie