Fonction en JavaScript qui ne peut être appelée qu'une seule fois

116

J'ai besoin de créer une fonction qui ne peut être exécutée qu'une seule fois, à chaque fois après la première, elle ne sera pas exécutée. Je connais de C ++ et Java les variables statiques qui peuvent faire le travail mais j'aimerais savoir s'il existe une manière plus élégante de le faire?

vlio20
la source
~ 8 ans plus tard, j'ai créé une demande de fonctionnalité à ma librairie de décorateurs ( github.com/vlio20/utils-decorators ) pour avoir un décorateur qui fera exactement cela ( github.com/vlio20/utils-decorators/issues/77 )
vlio20

Réponses:

221

Si par "ne sera pas exécuté" vous voulez dire "ne fera rien s'il est appelé plus d'une fois", vous pouvez créer une fermeture:

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

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

En réponse à un commentaire de @Vladloffe (maintenant supprimé): Avec une variable globale, un autre code pourrait réinitialiser la valeur de l'indicateur "exécuté" (quel que soit le nom que vous choisissez). Avec une fermeture, un autre code n'a aucun moyen de le faire, que ce soit accidentellement ou délibérément.

Comme d'autres réponses le soulignent ici, plusieurs bibliothèques (telles que Underscore et Ramda ) ont une petite fonction utilitaire (généralement nommée once()[*] ) qui accepte une fonction comme argument et renvoie une autre fonction qui appelle la fonction fournie exactement une fois, quelle que soit la manière dont plusieurs fois, la fonction retournée est appelée. La fonction retournée met également en cache la valeur renvoyée en premier par la fonction fournie et la renvoie lors des appels suivants.

Cependant, si vous n'utilisez pas une telle bibliothèque tierce, mais que vous voulez toujours une telle fonction utilitaire (plutôt que la solution nonce que j'ai proposée ci-dessus), elle est assez facile à implémenter. La plus belle version que j'ai vue est celle publiée par David Walsh :

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

Je serais enclin à changer fn = null;pour fn = context = null;. Il n'y a aucune raison pour que la fermeture maintienne une référence à contextune fois fna été appelée.

[*] Sachez cependant que d'autres bibliothèques, telles que cette extension Drupal de jQuery , peuvent avoir une fonction nommée once()qui fait quelque chose de très différent.

Ted Hopp
la source
1
fonctionne très bien, pouvez-vous expliquer la connexion derrière, comment var exécuté = false; oeuvres
Amr.Ayoub
@EgyCode - Ceci est bien expliqué dans la documentation MDN sur les fermetures .
Ted Hopp
désolé, je voulais dire logique, je n'ai jamais compris la variable booléenne et comment cela fonctionne dans ce cas exécuté
Amr.Ayoub
1
@EgyCode - Dans certains contextes (comme dans une ifexpression de test d'instruction), JavaScript attend l'une des valeurs trueou falseet le déroulement du programme réagit en fonction de la valeur trouvée lorsque l'expression est évaluée. Les opérateurs conditionnels comme ==toujours évalués à une valeur booléenne. Une variable peut également contenir soit trueou false. (Pour plus d'informations, consultez la documentation sur les booléens , les vérités et les faux .)
Ted Hopp
Je ne comprends pas pourquoi exécuté n'est pas réinitialisé à chaque nouvel appel. Quelqu'un peut-il l'expliquer, s'il vous plaît?
Juanse Cora
64

Remplacez-le par une fonction NOOP réutilisable (sans opération) .

// 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
}
Je déteste paresseux
la source
11
@fableal: En quoi est-ce inélégant? Encore une fois, il est très propre, nécessite moins de code et ne nécessite pas de nouvelle variable pour chaque fonction qui devrait être désactivée. Un "noop" est conçu exactement pour ce genre de situation.
Je déteste paresseux
1
@fableal: Je viens de regarder la réponse de hakra. Alors, faites une nouvelle fermeture et une nouvelle variable chaque fois que vous devez faire cela pour une nouvelle fonction? Vous avez une définition très drôle de «élégant».
Je déteste paresseux
2
Conformément à la réponse d'un avocat, il vous suffit de faire _.once (foo) ou _.once (bar), et les fonctions elles-mêmes n'ont pas besoin de savoir qu'elles ne sont exécutées qu'une seule fois (pas besoin de noop et pas besoin de le * = noop).
fableal
7
Pas vraiment la meilleure solution. Si vous transmettez cette fonction en tant que rappel, elle peut toujours être appelée plusieurs fois. Par exemple: setInterval(foo, 1000)- et déjà cela ne fonctionne plus. Vous écrasez simplement la référence dans la portée actuelle.
un chat
1
invalidateFonction réutilisable qui fonctionne avec setIntervaletc,.: Jsbin.com/vicipar/1/edit?js,console
Q20
32

Pointez sur une fonction vide une fois qu'elle a été appelée:

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>


Ou, comme ça:

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
la source
1
Cette solution s'inscrit bien plus dans l'esprit d'un langage très dynamique comme Javascript. Pourquoi définir des sémaphores, alors que l'on peut simplement vider la fonction une fois qu'elle a été utilisée?
Ivan Čurdinjaković
Très belle solution! Cette solution est également plus performante que l'approche de fermeture. Le seul "inconvénient" mineur est que vous devez synchroniser le nom de la fonction si le nom change.
Lionel
5
Le problème avec ceci est que s'il y a une autre référence à la fonction quelque part (par exemple, elle a été passée comme argument et cachée dans une autre variable quelque part - comme dans un appel à setInterval()) alors la référence répétera la fonctionnalité d'origine lorsqu'elle est appelée.
Ted Hopp
@TedHopp - voici un traitement spécial pour ces cas
vsync
1
Oui, c'est exactement la réponse de Bunyk sur ce fil. C'est aussi similaire à une fermeture (comme dans ma réponse ) mais en utilisant une propriété au lieu d'une variable de fermeture. Les deux cas sont assez différents de votre approche dans cette réponse.
Ted Hopp
25

UnderscoreJs a une fonction qui fait cela, 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;
    };
  };
avocat
la source
1
Il me semble drôle d' onceaccepter des arguments. Vous pouvez faire squareo = _.once(square); console.log(squareo(1)); console.log(squareo(2));et obtenir les 1deux appels à squareo. Suis-je bien compris?
aschmied le
@aschmied Vous avez raison - le résultat de l'ensemble d'arguments du premier appel sera mémorisé et renvoyé pour tous les autres appels, quel que soit le paramètre car la fonction sous-jacente n'est plus jamais appelée. Dans de tels cas, je ne suggère pas d'utiliser la _.onceméthode. Voir jsfiddle.net/631tgc5f/1
asawyer
1
@aschmied Ou je suppose utiliser un appel séparé à une fois par ensemble d'arguments. Je ne pense pas que ce soit vraiment destiné à ce genre d'utilisation.
asawyer
1
Pratique si vous utilisez déjà _; Je ne recommanderais pas de dépendre de la bibliothèque entière pour un si petit morceau de code.
Joe Shanahan
11

En parlant de variables statiques, c'est un peu comme la variante de fermeture:

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

once(); once(); 

Vous pouvez alors réinitialiser une fonction si vous le souhaitez:

once.done = false;
Bunyk
la source
4
var quit = false;

function something() {
    if(quit) {
       return;
    } 
    quit = true;
    ... other code....
}
Diodeus - James MacFarlane
la source
Voir la réponse de Ted Hopp. C'est une façon de définir la portée des variables au niveau des fonctions.
Diodeus - James MacFarlane
3

Vous pourriez simplement avoir la fonction "se supprimer"

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

    Once = undefined;
}

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

Mais ce n'est peut-être pas la meilleure réponse si vous ne voulez pas avaler des erreurs.

Vous pouvez également faire ceci:

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

    Once = function(){};
}

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

J'en ai besoin pour fonctionner comme un pointeur intelligent, s'il n'y a pas d'éléments de type A, il peut être exécuté, s'il y a un ou plusieurs éléments A, la fonction ne peut pas être exécutée.

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

    // do stuff
}
Shmiddty
la source
1
J'en ai besoin pour fonctionner comme un pointeur intelligent, s'il n'y a pas d'éléments de type A, il peut être exécuté, s'il y a un ou plusieurs éléments A, la fonction ne peut pas être exécutée.
vlio20
@VladIoffe Ce n'est pas ce que vous avez demandé.
Shmiddty
Cela ne fonctionnera pas si Onceest passé comme un rappel (par exemple, setInterval(Once, 100)). La fonction d'origine continuera à être appelée.
Ted Hopp
2

essaye ça

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

D'un mec nommé Crockford ... :)

function once(func) {
    return function () {
        var f = func;
        func = null;
        return f.apply(
            this,
            arguments
        );
    };
}
Jowen
la source
1
C'est génial si vous pensez que TypeError: Cannot read property 'apply' of nullc'est génial. C'est ce que vous obtenez la deuxième fois que vous appelez la fonction renvoyée.
Ted Hopp
2

invalidateFonction réutilisable qui fonctionne avec 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);

Essayez-le sur JSBin: https://jsbin.com/vicipar/edit?js,console

Variation de réponse de Bunyk

Q20
la source
1

Voici un exemple JSFiddle - http://jsfiddle.net/6yL6t/

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

Vous pourriez faire:

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

Et il ne fonctionnera qu'une seule fois :)

Aldekein
la source
1

La configuration initiale:

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
la source
1

Si vous utilisez Node.js ou que vous écrivez du JavaScript avec browserify, considérez le module npm "une fois" :

var once = require('once')

function load (file, cb) {
  cb = once(cb)
  loader.load('file')
  loader.once('load', cb)
  loader.once('error', cb)
}
orcaman
la source
1

Si vous voulez pouvoir réutiliser la fonction à l'avenir, cela fonctionne bien sur la base du code d'ed Hopp ci-dessus (je me rends compte que la question d'origine n'appelait pas cette fonctionnalité supplémentaire!):

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

La sortie ressemble à:

Hello World!
Reset
Hello World!
CMP
la source
1

décorateur simple et facile à écrire quand vous en avez besoin

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

en utilisant:

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

initializer() // 'initializing'
initializer() // nop
initializer() // nop
pery mimon
la source
0

Essayer d'utiliser la fonction de soulignement "une fois":

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

http://underscorejs.org/#once

Andrew Feng
la source
non, c'est trop moche quand on commence à l'appeler avec des arguments.
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
la source
0

Si vous utilisez Ramda, vous pouvez utiliser la fonction "une fois" .

Une citation de la documentation:

once Fonction (a… → b) → (a… → b) PARAMÈTRES Ajouté dans la v0.1.0

Accepte une fonction fn et retourne une fonction qui protège l'invocation de fn de telle sorte que fn ne puisse être appelée qu'une seule fois, quel que soit le nombre de fois où la fonction retournée est appelée. La première valeur calculée est renvoyée dans les appels suivants.

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

restez aussi simple que possible

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

Vous pouvez voir le résultat

résultat du script

Shreeketh K
la source
Si vous êtes à l'intérieur du module, utilisez simplement à la thisplace dewindow
Shreeketh K
0

JQuery permet d'appeler la fonction une seule fois en utilisant la méthode 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>

Implémentation à l'aide de la méthode JQuery sur () :

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>

Implémentation à l'aide de JS natif:

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
la source
-1
if (!window.doesThisOnce){
  function myFunction() {
    // do something
    window.doesThisOnce = true;
  };
};
atw
la source
C'est une mauvaise pratique de polluer la portée mondiale (aka window)
vlio20
Je suis d'accord avec vous, mais quelqu'un pourrait en tirer quelque chose.
partir du
Cela ne marche pas. Lorsque ce code est exécuté pour la première fois, la fonction est créée. Ensuite, lorsque la fonction est appelée, elle est exécutée et le global est défini sur false, mais la fonction peut toujours être appelée une prochaine fois.
trincot
Il n'est défini sur false nulle part.
partir du
-2

Celui-ci est utile pour éviter les boucles infinies (en utilisant jQuery):

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

Si vous vous inquiétez de la pollution de l'espace de noms, remplacez une longue chaîne aléatoire par "doIt".

Elliot Gorokhovsky
la source
-2

Cela aide à éviter une exécution collante

var done = false;

function doItOnce(func){
  if(!done){
    done = true;
    func()
  }
  setTimeout(function(){
    done = false;
  },1000)
}
Gleb Dolzikov
la source