Funkcja w JavaScript, którą można wywołać tylko raz

116

Muszę stworzyć funkcję, która może być wykonana tylko raz, za każdym razem po pierwszej nie zostanie wykonana. Wiem z C ++ i Javy o zmiennych statycznych, które mogą wykonać tę pracę, ale chciałbym wiedzieć, czy istnieje bardziej elegancki sposób, aby to zrobić?

vlio20
źródło
~ 8 lat później stworzyłem prośbę o dodanie funkcji do moich dekoratorów lib ( github.com/vlio20/utils-decorators ), aby mieć dekoratora, który zrobi dokładnie to ( github.com/vlio20/utils-decorators/issues/77 )
vlio20

Odpowiedzi:

221

Jeśli przez „nie zostanie wykonany” masz na myśli „nic nie zrobi, gdy zostanie wywołany więcej niż raz”, możesz utworzyć zamknięcie:

var something = (function() {
    var executed = false;
    return function() {
        if (!executed) {
            executed = true;
            // do something
        }
    };
})();

something(); // "do something" happens
something(); // nothing happens

W odpowiedzi na komentarz @Vladloffe (teraz usunięty): W przypadku zmiennej globalnej inny kod może zresetować wartość flagi „wykonane” (niezależnie od wybranej nazwy). W przypadku zamknięcia inny kod nie może tego zrobić, ani przypadkowo, ani celowo.

Jak wskazują inne odpowiedzi, kilka bibliotek (takich jak Underscore i Ramda ) ma małą funkcję użytkową (zwykle o nazwie once()[*] ), która przyjmuje funkcję jako argument i zwraca inną funkcję, która wywołuje podaną funkcję dokładnie raz, niezależnie od tego, jak wielokrotnie wywoływana jest funkcja zwracana. Zwracana funkcja również buforuje wartość zwróconą jako pierwsza przez podaną funkcję i zwraca ją przy kolejnych wywołaniach.

Jeśli jednak nie korzystasz z takiej biblioteki innej firmy, ale nadal chcesz mieć taką funkcję użytkową (zamiast rozwiązania jednorazowego, które zaproponowałem powyżej), jest to łatwe do wdrożenia. Najładniejsza wersja, jaką widziałem, to ta opublikowana przez Davida Walsha :

function once(fn, context) { 
    var result;
    return function() { 
        if (fn) {
            result = fn.apply(context || this, arguments);
            fn = null;
        }
        return result;
    };
}

Byłbym skłonny zmienić fn = null;się fn = context = null;. Nie ma powodu, dla którego domknięcie zachowuje odniesienie do contextkiedyś fnzostało wywołane.

[*] Należy jednak pamiętać, że inne biblioteki, takie jak to rozszerzenie Drupala do jQuery , mogą mieć funkcję o nazwie, once()która robi coś zupełnie innego.

Ted Hopp
źródło
1
działa bardzo dobrze, czy możesz wyjaśnić, skąd się bierze login, w jaki sposób var execute = false; prace
Amr.Ayoub
@EgyCode - jest to ładnie wyjaśnione w dokumentacji MDN dotyczącej zamknięć .
Ted Hopp
przepraszam, miałem na myśli logikę, nigdy nie rozumiałem
zmiennej
1
@EgyCode - w niektórych kontekstach (np. W ifwyrażeniu testu instrukcji) JavaScript oczekuje jednej z wartości truelub, falsea przepływ programu reaguje zgodnie z wartością znalezioną podczas obliczania wyrażenia. Operatory warunkowe, takie jak ==zawsze, obliczają wartość logiczną. Zmienna może również posiadać albo truealbo false. (Aby uzyskać więcej informacji, zapoznaj się z dokumentacją dotyczącą wartości logicznych , truey i falsey .)
Ted Hopp,
Nie rozumiem, dlaczego wykonanie nie jest resetowane przy każdym nowym wywołaniu. Czy ktoś może to wyjaśnić?
Juanse Cora
64

Zastąp go funkcją wielokrotnego użytku NOOP (brak działania) .

// this function does nothing
function noop() {};

function foo() {
    foo = noop; // swap the functions

    // do your thing
}

function bar() {
    bar = noop; // swap the functions

    // do your thing
}
Nienawidzę lenistwa
źródło
11
@fableal: Jak to jest nieeleganckie? Ponownie, jest bardzo czysty, wymaga mniej kodu i nie wymaga nowej zmiennej dla każdej funkcji, która powinna być wyłączona. „Noop” jest przeznaczony właśnie dla tego rodzaju sytuacji.
I Hate Lazy
1
@fableal: Właśnie spojrzałem na odpowiedź hakry. Czyli tworzyć nowe zamknięcie i zmienne za każdym razem, gdy trzeba to zrobić dla nowej funkcji? Masz bardzo zabawną definicję „eleganckiego”.
I Hate Lazy
2
Zgodnie z odpowiedzią użytkownika, wystarczyło wykonać _.once (foo) lub _.once (bar), a same funkcje nie muszą być świadome, że zostały uruchomione tylko raz (nie ma potrzeby * = noop).
bajka
7
Nie jest to najlepsze rozwiązanie. Jeśli przekazujesz tę funkcję jako wywołanie zwrotne, nadal można ją wywołać wiele razy. Na przykład: setInterval(foo, 1000)- i już to nie działa. Po prostu nadpisujesz odwołanie w bieżącym zakresie.
kot
1
invalidateFunkcja wielokrotnego użytku, która działa z setIntervalitp.: Jsbin.com/vicipar/1/edit?js,console
Q20
32

Wskaż pustą funkcję po jej wywołaniu:

function myFunc(){
     myFunc = function(){}; // kill it as soon as it was called
     console.log('call once and never again!'); // your stuff here
};
<button onClick=myFunc()>Call myFunc()</button>


Lub tak:

var myFunc = function func(){
     if( myFunc.fired ) return;
     myFunc.fired = true;
     console.log('called once and never again!'); // your stuff here
};

// even if referenced & "renamed"
((refToMyfunc)=>{
  setInterval(refToMyfunc, 1000);
})(myFunc)

vsync
źródło
1
To rozwiązanie jest znacznie bardziej w duchu wysoce dynamicznego języka, takiego jak Javascript. Po co ustawiać semafory, skoro można po prostu opróżnić funkcję po jej użyciu?
Ivan Čurdinjaković
Bardzo fajne rozwiązanie! To rozwiązanie działa również lepiej niż podejście zamykające. Jedyną drobną „wadą” jest to, że musisz zsynchronizować nazwę funkcji w przypadku jej zmiany.
Lionel
5
Problem polega na tym, że jeśli gdzieś istnieje inne odniesienie do funkcji (np. Zostało przekazane jako argument i gdzieś schowane w innej zmiennej - jak w wywołaniu setInterval()), to odwołanie powtórzy pierwotną funkcjonalność po wywołaniu.
Ted Hopp,
@TedHopp - oto specjalne traktowanie w tych przypadkach
vsync
1
Tak, dokładnie tak, jak odpowiedź Bunyka w tym wątku. Jest to również podobne do zamknięcia (jak w mojej odpowiedzi ), ale używa właściwości zamiast zmiennej zamknięcia. Oba przypadki różnią się od twojego podejścia w tej odpowiedzi.
Ted Hopp,
25

UnderscoreJs ma funkcję, która to robi, underscorejs.org/#once

  // Returns a function that will be executed at most one time, no matter how
  // often you call it. Useful for lazy initialization.
  _.once = function(func) {
    var ran = false, memo;
    return function() {
      if (ran) return memo;
      ran = true;
      memo = func.apply(this, arguments);
      func = null;
      return memo;
    };
  };
prawnik
źródło
1
Wydaje mi się zabawne onceprzyjmowanie argumentów. Możesz zrobić squareo = _.once(square); console.log(squareo(1)); console.log(squareo(2));i dostać 1za oba połączenia squareo. Czy dobrze to rozumiem?
aschmied
@aschmied Masz rację - wynik zestawu argumentów pierwszego wywołania zostanie zapamiętany i zwrócony dla wszystkich innych wywołań, niezależnie od parametru, ponieważ funkcja bazowa nigdy nie jest wywoływana ponownie. W takich przypadkach nie sugeruję stosowania tej _.oncemetody. Zobacz jsfiddle.net/631tgc5f/1
asawyer
1
@aschmied Lub myślę, że użyj oddzielnego wywołania raz na zestaw argumentów. Nie sądzę, żeby to było naprawdę przeznaczone do tego rodzaju zastosowań.
asawyer
1
Przydatne, jeśli już używasz _; Nie polecałbym polegać na całej bibliotece w przypadku tak małego fragmentu kodu.
Joe Shanahan
11

Mówiąc o zmiennych statycznych, wygląda to trochę jak wariant zamknięcia:

var once = function() {
    if(once.done) return;
    console.log('Doing this once!');
    once.done = true;
};

once(); once(); 

Następnie możesz zresetować funkcję, jeśli chcesz:

once.done = false;
Bunyk
źródło
4
var quit = false;

function something() {
    if(quit) {
       return;
    } 
    quit = true;
    ... other code....
}
Diodeus - James MacFarlane
źródło
Zobacz odpowiedź Teda Hoppa. Jest to sposób określania zakresu zmiennych na poziomie funkcji.
Diodeus - James MacFarlane
3

Możesz po prostu mieć funkcję „usuń się”

function Once(){
    console.log("run");

    Once = undefined;
}

Once();  // run
Once();  // Uncaught TypeError: undefined is not a function 

Ale to może nie być najlepsza odpowiedź, jeśli nie chcesz połykać błędów.

Możesz też to zrobić:

function Once(){
    console.log("run");

    Once = function(){};
}

Once(); // run
Once(); // nothing happens

Potrzebuję, aby działał jak inteligentny wskaźnik, jeśli nie ma elementów typu A, można go wykonać, jeśli jest jeden lub więcej elementów A, funkcja nie może zostać wykonana.

function Conditional(){
    if (!<no elements from type A>) return;

    // do stuff
}
Shmiddty
źródło
1
Potrzebuję, aby działał jak inteligentny wskaźnik, jeśli nie ma elementów typu A, można go wykonać, jeśli istnieje jeden lub więcej elementów A, funkcja nie może zostać wykonana.
vlio20
@VladIoffe Nie o to pytałeś.
Shmiddty
To nie zadziała, jeśli Oncezostanie przekazane jako oddzwonienie (np setInterval(Once, 100).). Oryginalna funkcja będzie nadal wywoływana.
Ted Hopp
2

Spróbuj tego

var fun = (function() {
  var called = false;
  return function() {
    if (!called) {
      console.log("I  called");
      called = true;
    }
  }
})()
Database_Query
źródło
2

Od jakiegoś gościa imieniem Crockford ... :)

function once(func) {
    return function () {
        var f = func;
        func = null;
        return f.apply(
            this,
            arguments
        );
    };
}
Jowen
źródło
1
To świetnie, jeśli uważasz, że TypeError: Cannot read property 'apply' of nullto świetnie. To jest to, co otrzymasz po drugim wywołaniu zwróconej funkcji.
Ted Hopp
2

invalidateFunkcja wielokrotnego użytku, która współpracuje z setInterval:

var myFunc = function (){
  if (invalidate(arguments)) return;
  console.log('called once and never again!'); // your stuff here
};

const invalidate = function(a) {
  var fired = a.callee.fired;
  a.callee.fired = true;
  return fired;
}

setInterval(myFunc, 1000);

Wypróbuj na JSBin: https://jsbin.com/vicipar/edit?js,console

Odmiana odpowiedź od Bunyk

Pytanie 20
źródło
1

Oto przykład JSFiddle - http://jsfiddle.net/6yL6t/

A kod:

function hashCode(str) {
    var hash = 0, i, chr, len;
    if (str.length == 0) return hash;
    for (i = 0, len = str.length; i < len; i++) {
        chr   = str.charCodeAt(i);
        hash  = ((hash << 5) - hash) + chr;
        hash |= 0; // Convert to 32bit integer
    }
    return hash;
}

var onceHashes = {};

function once(func) {
    var unique = hashCode(func.toString().match(/function[^{]+\{([\s\S]*)\}$/)[1]);

    if (!onceHashes[unique]) {
        onceHashes[unique] = true;
        func();
    }
}

Mógłbyś:

for (var i=0; i<10; i++) {
    once(function() {
        alert(i);
    });
}

I będzie działać tylko raz :)

Aldekein
źródło
1

Początkowe ustawienia:

var once = function( once_fn ) {
    var ret, is_called;
    // return new function which is our control function 
    // to make sure once_fn is only called once:
    return function(arg1, arg2, arg3) {
        if ( is_called ) return ret;
        is_called = true;
        // return the result from once_fn and store to so we can return it multiply times:
        // you might wanna look at Function.prototype.apply:
        ret = once_fn(arg1, arg2, arg3);
        return ret;
    };
}
andlrc
źródło
1

Jeśli korzystasz z Node.js lub piszesz JavaScript w przeglądarce browserify, rozważ moduł jednorazowy npm :

var once = require('once')

function load (file, cb) {
  cb = once(cb)
  loader.load('file')
  loader.once('load', cb)
  loader.once('error', cb)
}
orcaman
źródło
1

Jeśli chcesz móc ponownie użyć tej funkcji w przyszłości, działa to dobrze w oparciu o powyższy kod ed Hoppa (zdaję sobie sprawę, że pierwotne pytanie nie wymagało tej dodatkowej funkcji!):

   var something = (function() {
   var executed = false;              
    return function(value) {
        // if an argument is not present then
        if(arguments.length == 0) {               
            if (!executed) {
            executed = true;
            //Do stuff here only once unless reset
            console.log("Hello World!");
            }
            else return;

        } else {
            // otherwise allow the function to fire again
            executed = value;
            return;
        }       
    }
})();

something();//Hello World!
something();
something();
console.log("Reset"); //Reset
something(false);
something();//Hello World!
something();
something();

Wynik wygląda następująco:

Hello World!
Reset
Hello World!
CMP
źródło
1

prosty dekorator, który łatwo napisać, gdy potrzebujesz

function one(func) {
  return function () {
     func && func.apply(this, arguments);
     func = null;
  }
}

za pomocą:

var initializer= one( _ =>{
      console.log('initializing')
  })

initializer() // 'initializing'
initializer() // nop
initializer() // nop
pery mimon
źródło
0

Próba użycia podkreślenia „raz”:

var initialize = _.once(createApplication);
initialize();
initialize();
// Application is only created once.

http://underscorejs.org/#once

Andrew Feng
źródło
nie, to zbyt brzydkie, kiedy zaczynasz nazywać to z argumentami.
vsync
0
var init = function() {
    console.log("logges only once");
    init = false;
}; 

if(init) { init(); }

/* next time executing init() will cause error because now init is 
   -equal to false, thus typing init will return false; */
RegarBoy
źródło
0

Jeśli używasz Ramdy, możesz użyć funkcji „raz” .

Cytat z dokumentacji:

raz Funkcja (a… → b) → (a… → b) PARAMETRY Dodane w v0.1.0

Akceptuje funkcję fn i zwraca funkcję, która chroni wywołanie fn tak, że fn można wywołać tylko raz, niezależnie od tego, ile razy jest wywoływana zwracana funkcja. Pierwsza obliczona wartość jest zwracana w kolejnych wywołaniach.

var addOneOnce = R.once(x => x + 1);
addOneOnce(10); //=> 11
addOneOnce(addOneOnce(50)); //=> 11
David
źródło
0

postaraj się, aby było to tak proste, jak to tylko możliwe

function sree(){
  console.log('hey');
  window.sree = _=>{};
}

Możesz zobaczyć wynik

wynik skryptu

Shreeketh K.
źródło
Jeśli jesteś w module, po prostu użyj thiszamiastwindow
Shreeketh K
0

JQuery pozwala wywołać funkcję tylko raz za pomocą metody one () :

let func = function() {
  console.log('Calling just once!');
}
  
let elem = $('#example');
  
elem.one('click', func);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div>
  <p>Function that can be called only once</p>
  <button id="example" >JQuery one()</button>
</div>

Implementacja z wykorzystaniem metody JQuery on () :

let func = function(e) {
  console.log('Calling just once!');
  $(e.target).off(e.type, func)
}
  
let elem = $('#example');
  
elem.on('click', func);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div>
  <p>Function that can be called only once</p>
  <button id="example" >JQuery on()</button>
</div>

Implementacja przy użyciu natywnego JS:

let func = function(e) {
  console.log('Calling just once!');
  e.target.removeEventListener(e.type, func);
}
  
let elem = document.getElementById('example');
  
elem.addEventListener('click', func);
<div>
  <p>Functions that can be called only once</p>
  <button id="example" >ECMAScript addEventListener</button>
</div>

Nikita
źródło
-1
if (!window.doesThisOnce){
  function myFunction() {
    // do something
    window.doesThisOnce = true;
  };
};
atw
źródło
Zanieczyszczanie zasięgu globalnego (czyli okna) to zła praktyka
vlio20,
Zgadzam się z tobą, ale ktoś może coś z tego wyciągnąć.
atw
To nie działa. Kiedy ten kod jest wykonywany po raz pierwszy, tworzona jest funkcja. Następnie, gdy funkcja jest wywoływana, jest wykonywana, a global jest ustawiany na false, ale funkcja może zostać wywołana następnym razem.
trincot
Nigdzie nie jest ustawiony na fałsz.
atw
-2

Ten jest przydatny do zapobiegania nieskończonym pętlom (przy użyciu jQuery):

<script>
var doIt = true;
if(doIt){
  // do stuff
  $('body').html(String($('body').html()).replace("var doIt = true;", 
                                                  "var doIt = false;"));
} 
</script>

Jeśli martwisz się zanieczyszczeniem przestrzeni nazw, zamień długi, losowy ciąg na „doIt”.

Elliot Gorokhovsky
źródło
-2

Pomaga zapobiegać lepkiemu wykonaniu

var done = false;

function doItOnce(func){
  if(!done){
    done = true;
    func()
  }
  setTimeout(function(){
    done = false;
  },1000)
}
Gleb Dolzikov
źródło