Funktion in JavaScript, die nur einmal aufgerufen werden kann


116

Ich muss eine Funktion erstellen, die nur einmal ausgeführt werden kann, jedes Mal nach dem ersten wird sie nicht ausgeführt. Ich kenne aus C ++ und Java statische Variablen, die die Arbeit erledigen können, aber ich würde gerne wissen, ob es einen eleganteren Weg gibt, dies zu tun?


~ 8 Jahre später erstellte ich eine Feature-Anfrage an meine Dekorateure lib ( github.com/vlio20/utils-decorators ), um einen Dekorateur zu haben, der genau das tut ( github.com/vlio20/utils-decorators/issues/77 )
vlio20

Antworten:


221

Wenn mit "wird nicht ausgeführt" gemeint ist, dass "bei mehrmaligem Aufruf nichts unternommen wird", können Sie einen Abschluss erstellen:

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

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

Antwort auf einen Kommentar von @Vladloffe (jetzt gelöscht): Mit einer globalen Variablen könnte ein anderer Code den Wert des Flags "ausgeführt" zurücksetzen (welcher Name auch immer Sie dafür wählen). Bei einem Abschluss kann ein anderer Code dies weder versehentlich noch absichtlich tun.

Wie andere Antworten hier zeigen, verfügen mehrere Bibliotheken (wie Underscore und Ramda ) über eine kleine Dienstprogrammfunktion (normalerweise mit dem Namen once()[*] ), die eine Funktion als Argument akzeptiert und eine andere Funktion zurückgibt, die die angegebene Funktion genau einmal aufruft, unabhängig davon, wie oft wird die zurückgegebene Funktion aufgerufen. Die zurückgegebene Funktion speichert auch den Wert zwischen, der zuerst von der angegebenen Funktion zurückgegeben wurde, und gibt diesen bei nachfolgenden Aufrufen zurück.

Wenn Sie jedoch keine solche Bibliothek eines Drittanbieters verwenden, aber dennoch eine solche Dienstprogrammfunktion (anstelle der oben angebotenen Nonce-Lösung) möchten, ist die Implementierung einfach genug. Die schönste Version, die ich gesehen habe, ist die von David Walsh :

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

Ich würde geneigt sein , zu ändern , fn = null;zu fn = context = null;. Es gibt keinen Grund für die Schließung, einen Verweis auf contexteinmal fnaufgerufene Anrufe beizubehalten .

[*] Beachten Sie jedoch, dass andere Bibliotheken, wie diese Drupal-Erweiterung für jQuery , möglicherweise eine Funktion mit dem Namen haben once(), die etwas ganz anderes bewirkt.


1
funktioniert sehr gut, kannst du das Login dahinter erklären, wie wird var ausgeführt = false; Werke
Amr.Ayoub

@EgyCode - Dies wird in der MDN-Dokumentation zu Schließungen ausführlich erläutert .
Ted Hopp

Entschuldigung, ich meinte Logik, ich habe Boolesche Var nie verstanden und wie es in diesem Fall funktioniert ausgeführt
Amr.Ayoub

1
@EgyCode - In bestimmten Kontexten (wie in einem ifAnweisungstestausdruck) erwartet JavaScript einen der Werte trueoder falseund der Programmablauf reagiert entsprechend dem Wert, der bei der Auswertung des Ausdrucks gefunden wurde. Bedingte Operatoren werden wie ==immer zu einem booleschen Wert ausgewertet. Eine Variable kann auch entweder trueoder enthalten false. (Weitere Informationen finden Sie in der Dokumentation zu Boolean , Truthy und Falsey .)
Ted Hopp

Ich verstehe nicht, warum ausgeführt nicht bei jedem neuen Aufruf zurückgesetzt wird. Kann es bitte jemand erklären?
Juanse Cora

64

Ersetzen Sie es durch eine wiederverwendbare NOOP-Funktion (keine Operation) .

// 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
}

11
@fableal: Wie ist das unelegant? Auch hier ist es sehr sauber, erfordert weniger Code und erfordert nicht für jede Funktion, die deaktiviert werden soll, eine neue Variable. Ein "Noop" ist genau für diese Art von Situation ausgelegt.
Ich hasse faul

1
@fableal: Ich habe mir nur die Antwort von hakra angesehen. Machen Sie also jedes Mal einen neuen Abschluss und eine neue Variable, wenn Sie dies für eine neue Funktion tun müssen? Sie haben eine sehr lustige Definition von "elegant".
Ich hasse Lazy

2
Entsprechend der Antwort von asawyer mussten Sie nur _.once (foo) oder _.once (bar) ausführen, und die Funktionen selbst müssen nicht wissen, dass sie nur einmal ausgeführt werden (keine Notwendigkeit für noop und keine Notwendigkeit für das * = noop).
Fableal

7
Nicht wirklich die beste Lösung. Wenn Sie diese Funktion als Rückruf übergeben, kann sie dennoch mehrmals aufgerufen werden. Zum Beispiel: setInterval(foo, 1000)- und das funktioniert schon nicht mehr. Sie überschreiben nur die Referenz im aktuellen Bereich.
eine Katze

1
Wiederverwendbare invalidateFunktion, die mit setIntervaletc funktioniert .: Jsbin.com/vicipar/1/edit?js,console
user300375

32

Zeigen Sie auf eine leere Funktion, sobald sie aufgerufen wurde:

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>


Oder wie folgt:

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)


1
Diese Lösung ist viel mehr im Sinne einer hochdynamischen Sprache wie Javascript. Warum Semaphore setzen, wenn Sie die Funktion nach ihrer Verwendung einfach leeren können?
Ivan Čurdinjaković

Sehr schöne Lösung! Diese Lösung bietet auch eine bessere Leistung als der Verschlussansatz. Der einzige kleine "Nachteil" ist, dass Sie den Funktionsnamen synchron halten müssen, wenn sich der Name ändert.
Lionel

5
Das Problem dabei ist, dass, wenn irgendwo ein anderer Verweis auf die Funktion vorhanden ist (z. B. als Argument übergeben und irgendwo in einer anderen Variablen gespeichert wurde - wie bei einem Aufruf von setInterval()), der Verweis beim Aufruf die ursprüngliche Funktionalität wiederholt.
Ted Hopp

@ TedHopp - hier ist eine spezielle Behandlung für diese Fälle
vsync

1
Ja, das ist genau wie Bunyks Antwort auf diesen Thread. Es ähnelt auch einem Abschluss (wie in meiner Antwort ), verwendet jedoch eine Eigenschaft anstelle einer Abschlussvariablen. Beide Fälle unterscheiden sich stark von Ihrem Ansatz in dieser Antwort.
Ted Hopp

25

UnderscoreJs hat eine Funktion, die das macht, 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;
    };
  };

1
Es scheint mir lustig once, Argumente akzeptiert zu haben . Sie könnten für beide Anrufe tun squareo = _.once(square); console.log(squareo(1)); console.log(squareo(2));und bekommen . Verstehe ich das richtig? 1squareo
aschmied

@aschmied Sie haben Recht - das Ergebnis der Argumente des ersten Aufrufs wird gespeichert und für alle anderen Aufrufe unabhängig vom Parameter zurückgegeben, da die zugrunde liegende Funktion nie wieder aufgerufen wird. In solchen Fällen empfehle ich nicht, die _.onceMethode zu verwenden. Siehe jsfiddle.net/631tgc5f/1
asawyer

1
@aschmied Oder ich denke, verwenden Sie einen separaten Aufruf, um einmal pro Argumentsatz. Ich denke nicht, dass dies wirklich für diese Art von Verwendung gedacht ist.
Asawyer

1
Praktisch, wenn Sie bereits verwenden _; Ich würde nicht empfehlen, abhängig von der gesamten Bibliothek für so ein kleines Stück Code.
Joe Shanahan

11

Wenn wir über statische Variablen sprechen, ist dies ein bisschen wie eine Abschlussvariante:

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

once(); once(); 

Sie können dann eine Funktion zurücksetzen, wenn Sie möchten:

once.done = false;


3

Sie könnten einfach die Funktion "sich selbst entfernen" haben

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

    Once = undefined;
}

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

Dies ist jedoch möglicherweise nicht die beste Antwort, wenn Sie keine Fehler verschlucken möchten.

Sie können dies auch tun:

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

    Once = function(){};
}

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

Ich brauche es, um wie ein intelligenter Zeiger zu funktionieren. Wenn es keine Elemente vom Typ A gibt, kann es ausgeführt werden. Wenn es ein oder mehrere A-Elemente gibt, kann die Funktion nicht ausgeführt werden.

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

    // do stuff
}

1
Ich brauche es, um wie ein intelligenter Zeiger zu funktionieren. Wenn es keine Elemente vom Typ A gibt, kann es ausgeführt werden. Wenn es ein oder mehrere A-Elemente gibt, kann die Funktion nicht ausgeführt werden.
vlio20

@VladIoffe Das hast du nicht gefragt.
Shmiddty

Dies funktioniert nicht, wenn Oncees als Rückruf übergeben wird (z setInterval(Once, 100). B. ). Die ursprüngliche Funktion wird weiterhin aufgerufen.
Ted Hopp

2

Versuche dies

var fun = (function() {
  var called = false;
  return function() {
    if (!called) {
      console.log("I  called");
      called = true;
    }
  }
})()

2

Von einem Typen namens Crockford ... :)

function once(func) {
    return function () {
        var f = func;
        func = null;
        return f.apply(
            this,
            arguments
        );
    };
}

1
Das ist großartig, wenn Sie das TypeError: Cannot read property 'apply' of nullgroßartig finden. Das bekommen Sie, wenn Sie die zurückgegebene Funktion zum zweiten Mal aufrufen.
Ted Hopp

2

Wiederverwendbare invalidateFunktion, die funktioniert mit 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);

Probieren Sie es auf JSBin aus: https://jsbin.com/vicipar/edit?js,console

Variation der Antwort von Bunyk


1

Hier ist ein Beispiel für JSFiddle - http://jsfiddle.net/6yL6t/

Und der Code:

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

Du könntest es tun:

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

Und es wird nur einmal laufen :)


1

Ersteinrichtung:

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

1

Wenn Sie Node.js verwenden oder JavaScript mit browserify schreiben, ziehen Sie das npm-Modul "einmal" in Betracht :

var once = require('once')

function load (file, cb) {
  cb = once(cb)
  loader.load('file')
  loader.once('load', cb)
  loader.once('error', cb)
}

1

Wenn Sie die Funktion in Zukunft wiederverwenden möchten, funktioniert dies gut, basierend auf dem obigen Code von ed Hopp (mir ist klar, dass die ursprüngliche Frage diese zusätzliche Funktion nicht erforderte!):

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

Die Ausgabe sieht aus wie:

Hello World!
Reset
Hello World!

1

Einfacher Dekorateur, der bei Bedarf einfach zu schreiben ist

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

mit:

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

initializer() // 'initializing'
initializer() // nop
initializer() // nop

0

Versuch, die Unterstrichfunktion "einmal" zu verwenden:

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

http://underscorejs.org/#once


Nein, es ist zu hässlich, wenn man es mit Argumenten aufruft.
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; */

0

Wenn Sie Ramda verwenden, können Sie die Funktion "einmal" verwenden .

Ein Zitat aus der Dokumentation:

einmal Funktion (a… → b) → (a… → b) PARAMETER Hinzugefügt in v0.1.0

Akzeptiert eine Funktion fn und gibt eine Funktion zurück, die den Aufruf von fn so schützt, dass fn immer nur einmal aufgerufen werden kann, unabhängig davon, wie oft die zurückgegebene Funktion aufgerufen wird. Der erste berechnete Wert wird in nachfolgenden Aufrufen zurückgegeben.

var addOneOnce = R.once(x => x + 1);
addOneOnce(10); //=> 11
addOneOnce(addOneOnce(50)); //=> 11

0

Halte es so einfach wie möglich

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

Sie können das Ergebnis sehen

Skriptergebnis


Wenn Sie sich im Modul befinden, verwenden Sie einfach thisanstelle vonwindow
Shreeketh K

0

Mit JQuery kann die Funktion nur einmal mit der Methode one () aufgerufen werden :

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>

Implementierung mit der JQuery-Methode 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>

Implementierung mit nativem 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>


-1
if (!window.doesThisOnce){
  function myFunction() {
    // do something
    window.doesThisOnce = true;
  };
};

Es ist eine schlechte Praxis, den globalen Geltungsbereich (auch bekannt als Fenster) zu verschmutzen
vlio20

Ich stimme Ihnen zu, aber jemand könnte etwas daraus machen.
atw

Das funktioniert nicht. Wenn dieser Code zum ersten Mal ausgeführt wird, wird die Funktion erstellt. Wenn die Funktion dann aufgerufen wird, wird sie ausgeführt und die globale Funktion wird auf false gesetzt, aber die Funktion kann beim nächsten Mal immer noch aufgerufen werden.
Trincot

Es ist nirgendwo auf false gesetzt.
atw

-2

Dieser ist nützlich, um Endlosschleifen zu verhindern (mit jQuery):

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

Wenn Sie sich Sorgen über die Verschmutzung von Namespaces machen, ersetzen Sie "doIt" durch eine lange, zufällige Zeichenfolge.


-2

Es hilft, eine klebrige Ausführung zu verhindern

var done = false;

function doItOnce(func){
  if(!done){
    done = true;
    func()
  }
  setTimeout(function(){
    done = false;
  },1000)
}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.