Funzione in JavaScript che può essere chiamata una sola volta


116

Devo creare una funzione che possa essere eseguita una sola volta, ogni volta dopo la prima non verrà eseguita. Conosco da C ++ e Java le variabili statiche che possono fare il lavoro, ma vorrei sapere se esiste un modo più elegante per farlo?


~ 8 anni dopo ho creato una richiesta di funzionalità per i miei decoratori lib ( github.com/vlio20/utils-decorators ) per avere un decoratore che farà esattamente questo ( github.com/vlio20/utils-decorators/issues/77 )
vlio20

Risposte:


221

Se con "non verrà eseguito" intendi "non farà nulla se chiamato più di una volta", puoi creare una chiusura:

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

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

In risposta a un commento di @Vladloffe (ora eliminato): con una variabile globale, un altro codice potrebbe reimpostare il valore del flag "eseguito" (qualunque sia il nome scelto). Con una chiusura, un altro codice non ha modo di farlo, accidentalmente o deliberatamente.

Come altre risposte qui sottolineano, diverse librerie (come Underscore e Ramda ) hanno una piccola funzione di utilità (tipicamente chiamata once()[*] ) che accetta una funzione come argomento e restituisce un'altra funzione che chiama la funzione fornita esattamente una volta, indipendentemente da come molte volte viene chiamata la funzione restituita. La funzione restituita memorizza anche nella cache il valore restituito per primo dalla funzione fornita e lo restituisce nelle chiamate successive.

Tuttavia, se non stai utilizzando una libreria di terze parti, ma desideri comunque una tale funzione di utilità (piuttosto che la soluzione nonce che ho offerto sopra), è abbastanza facile da implementare. La versione più bella che ho visto è questa pubblicata da David Walsh :

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

Sarei propenso a cambiare fn = null;in fn = context = null;. Non c'è motivo per la chiusura di mantenere un riferimento a contextuna volta che fnè stato chiamato.

[*] Tieni presente, tuttavia, che altre librerie, come questa estensione Drupal a jQuery , potrebbero avere una funzione denominata once()che fa qualcosa di completamente diverso.


1
funziona molto bene, puoi spiegare il login dietro di esso, come funziona var execute = false; funziona
Amr.Ayoub

@EgyCode - Questo è spiegato bene nella documentazione MDN sulle chiusure .
Ted Hopp

scusa, intendevo logica, non ho mai capito la var booleana e come funziona in quel caso eseguito
Amr.Ayoub

1
@EgyCode - In alcuni contesti (come in ifun'espressione di test di istruzione), JavaScript si aspetta uno dei valori trueo falsee il flusso del programma reagisce in base al valore trovato quando l'espressione viene valutata. Gli operatori condizionali come ==restituiscono sempre un valore booleano. Una variabile può contenere anche trueo false. (Per ulteriori informazioni, vedere la documentazione su booleano , veritiero e falso .)
Ted Hopp

Non capisco perché l'esecuzione non venga ripristinata ad ogni nuova chiamata. Qualcuno può spiegarlo, per favore?
Juanse Cora

64

Sostituirlo con una funzione NOOP (nessuna operazione) riutilizzabile .

// 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: come è inelegante? Di nuovo, è molto pulito, richiede meno codice e non richiede una nuova variabile per ogni funzione che dovrebbe essere disabilitata. Un "noop" è progettato esattamente per questo tipo di situazione.
I Hate Lazy

1
@fableal: ho appena guardato la risposta di hakra. Quindi fare una nuova chiusura e variabile ogni volta che è necessario eseguire questa operazione su una nuova funzione? Hai una definizione molto divertente di "elegante".
I Hate Lazy

2
Di conseguenza, alla risposta di un awyer, era necessario eseguire solo _.once (foo) o _.once (bar) e le funzioni stesse non devono essere consapevoli di essere eseguite una sola volta (non c'è bisogno del noop e non c'è bisogno di il * = noop).
favoloso

7
Non proprio la soluzione migliore. Se stai passando questa funzione come callback, può ancora essere chiamata più volte. Ad esempio: setInterval(foo, 1000)- e già questo non funziona più. Stai solo sovrascrivendo il riferimento nell'ambito corrente.
un gatto

1
invalidateFunzione riutilizzabile che funziona con setIntervalecc.: Jsbin.com/vicipar/1/edit?js,console
Q20

32

Puntare a una funzione vuota una volta che è stata chiamata:

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>


Oppure, in questo modo:

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
Questa soluzione è molto più nello spirito di un linguaggio altamente dinamico come Javascript. Perché impostare i semafori, quando puoi semplicemente svuotare la funzione una volta che è stata utilizzata?
Ivan Čurdinjaković

Soluzione molto bella! Questa soluzione ha anche prestazioni migliori rispetto all'approccio di chiusura. L'unico "svantaggio" minore è che è necessario mantenere sincronizzato il nome della funzione se il nome cambia.
Lionel

5
Il problema con questo è che se c'è un altro riferimento alla funzione da qualche parte (ad es. È stato passato come argomento e nascosto in un'altra variabile da qualche parte - come in una chiamata a setInterval()), il riferimento ripeterà la funzionalità originale quando viene chiamato.
Ted Hopp

@TedHopp - ecco un trattamento speciale per questi casi
vsync

1
Sì, è esattamente come la risposta di Bunyk su questo thread. È anche simile a una chiusura (come nella mia risposta ) ma utilizza una proprietà invece di una variabile di chiusura. Entrambi i casi sono abbastanza diversi dal tuo approccio in questa risposta.
Ted Hopp

25

UnderscoreJs ha una funzione che lo fa, 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
Mi sembra divertente dover onceaccettare argomenti. Potresti fare squareo = _.once(square); console.log(squareo(1)); console.log(squareo(2));e ottenere 1entrambe le chiamate a squareo. Lo capisco bene?
aschmied

@aschmied Hai ragione: il risultato del set di argomenti della prima chiamata verrà memorizzato e restituito per tutte le altre chiamate indipendentemente dal parametro poiché la funzione sottostante non viene mai più chiamata. In casi come questo non suggerisco di utilizzare il _.oncemetodo. Vedi jsfiddle.net/631tgc5f/1
asawyer

1
@ aschmied O immagino di utilizzare una chiamata separata a una volta per set di argomenti. Non credo che questo sia realmente inteso per quel tipo di utilizzo.
avvocato

1
Comodo se stai già utilizzando _; Non consiglierei di dipendere dall'intera libreria per un po 'di codice così piccolo.
Joe Shanahan

11

Parlando di variabili statiche, questa è un po 'come la variante di chiusura:

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

once(); once(); 

È quindi possibile ripristinare una funzione se lo si desidera:

once.done = false;


3

Potresti semplicemente avere la funzione "rimuovi se stesso"

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

    Once = undefined;
}

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

Ma questa potrebbe non essere la risposta migliore se non vuoi inghiottire errori.

Potresti anche fare questo:

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

    Once = function(){};
}

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

Ho bisogno che funzioni come un puntatore intelligente, se non ci sono elementi del tipo A può essere eseguito, se ci sono uno o più elementi A la funzione non può essere eseguita.

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

    // do stuff
}

1
Ho bisogno che funzioni come un puntatore intelligente, se non ci sono elementi del tipo A può essere eseguito, se ci sono uno o più elementi A la funzione non può essere eseguita.
vlio20

@VladIoffe Non è quello che hai chiesto.
Shmiddty

Questo non funzionerà se Onceviene passato come call-back (ad esempio setInterval(Once, 100)). La funzione originale continuerà a essere chiamata.
Ted Hopp

2

prova questo

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

2

Da un tizio di nome Crockford ... :)

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

1
Questo è fantastico se pensi che TypeError: Cannot read property 'apply' of nullsia fantastico. Questo è ciò che ottieni la seconda volta che invochi la funzione restituita.
Ted Hopp

2

invalidateFunzione riutilizzabile che funziona con 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);

Provalo su JSBin: https://jsbin.com/vicipar/edit?js,console

Variazione della risposta di Bunyk


1

Ecco un esempio di JSFiddle: http://jsfiddle.net/6yL6t/

E il codice:

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

Potresti fare:

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

E verrà eseguito solo una volta :)


1

Configurazione iniziale:

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

Se utilizzi Node.js o scrivi JavaScript con browserify, considera il modulo npm "una volta" :

var once = require('once')

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

1

Se vuoi essere in grado di riutilizzare la funzione in futuro, questo funziona bene sulla base del codice di ed Hopp sopra (mi rendo conto che la domanda originale non richiedeva questa funzione extra!):

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

L'output è simile a:

Hello World!
Reset
Hello World!

1

decoratore semplice che facile da scrivere quando serve

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

utilizzando:

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

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

0

Tentativo di utilizzare la funzione di sottolineatura "una volta":

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

http://underscorejs.org/#once


nah, è troppo brutto quando inizi a chiamarlo con argomenti.
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

Se stai usando Ramda, puoi usare la funzione "once" .

Una citazione dalla documentazione:

una volta Funzione (a… → b) → (a… → b) PARAMETRI Aggiunto nella v0.1.0

Accetta una funzione fn e restituisce una funzione che protegge l'invocazione di fn in modo tale che fn possa essere chiamata solo una volta, indipendentemente dal numero di volte che viene invocata la funzione restituita. Il primo valore calcolato viene restituito nelle chiamate successive.

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

0

mantenerlo il più semplice possibile

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

Puoi vedere il risultato

risultato dello script


Se sei all'interno del modulo, usa semplicemente al thisposto diwindow
Shreeketh K

0

JQuery permette di chiamare la funzione una sola volta utilizzando il metodo 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>

Implementazione utilizzando il metodo JQuery su () :

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>

Implementazione utilizzando JS nativo:

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

È una cattiva pratica inquinare l'ambito globale (aka finestra)
vlio20

Sono d'accordo con te ma qualcuno potrebbe ricavarne qualcosa.
ATW

Questo non funziona. Quando quel codice viene eseguito per la prima volta, la funzione viene creata. Quindi, quando la funzione viene chiamata, viene eseguita e il globale viene impostato su false, ma la funzione può ancora essere chiamata una volta successiva.
trincot

Non è impostato su false da nessuna parte.
ATW

-2

Questo è utile per prevenire cicli infiniti (usando jQuery):

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

Se sei preoccupato per l'inquinamento dello spazio dei nomi, sostituisci una lunga stringa casuale per "doIt".


-2

Aiuta a prevenire l'esecuzione appiccicosa

var done = false;

function doItOnce(func){
  if(!done){
    done = true;
    func()
  }
  setTimeout(function(){
    done = false;
  },1000)
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.