2010-09-09 10 views
30

Esiste un modo per eseguire solo un codice JavaScript ONCE, senza utilizzare le variabili flag booleane per ricordare se è già stato eseguito o no?JavaScript - eseguito una volta senza booleans

particolare non qualcosa di simile:

var alreadyRan = false; 
function runOnce() { 
    if (alreadyRan) { 
    return; 
    } 
    alreadyRan = true; 

    /* do stuff here */ 

} 

ho intenzione di avere un sacco di questi tipi di funzioni e mantenendo tutti i valori booleani sarei disordinato ...

+2

È necessario chiarire la domanda un po 'di più. –

+1

Basta non usare i loop? – irishbuzz

+0

Non chiamarlo due volte? – EboMike

risposta

75

Un modo alternativo che sovrascrive una funzione quando viene eseguita in modo che venga eseguita una sola volta.

function useThisFunctionOnce(){ 
    // overwrite this function, so it will be executed only once 
    useThisFunctionOnce = Function(""); 
    // real code below 
    alert("Hi!"); 
} 

// displays "Hi!" 
useThisFunctionOnce(); 
// does nothing 
useThisFunctionOnce(); 

'utile' esempio:

var preferences = {}; 
function read_preferences(){ 
    // read preferences once 
    read_preferences = Function(""); 
    // load preferences from storage and save it in 'preferences' 
} 
function readPreference(pref_name){ 
    read_prefences(); 
    return preferences.hasOwnProperty(pref_name) ? preferences[pref_name] : ''; 
} 
if(readPreference('like_javascript') != 'yes'){ 
    alert("What's wrong wth you?!"); 
} 
alert(readPreference('is_stupid') ? "Stupid!" : ":)"); 

Edit: come CMS sottolineato, basta sovrascrivere la vecchia funzione con function(){} creerà una chiusura in cui esistono ancora vecchi variabili. Per ovviare a questo problema, function(){} viene sostituito da Function(""). Ciò creerà una funzione vuota nell'ambito globale, evitando una chiusura.

+2

+1 Per una soluzione intelligente! –

+6

+1 davvero! Dando un nuovo significato a "questa funzione si autodistruggerà dopo il primo utilizzo". – EboMike

+0

molto pulito anche se non riesco a vedere il valore di questo. anche se non è colpa tua +1 – mcgrailm

-2

Se non è vincolata ad un evento, il codice è di solito una volta correva

+1

E i loop? – Daff

+0

abbastanza semplice, considerando la domanda. – Zlatev

9

mi piace implementazione di Lekensteyn, ma si potrebbe anche semplicemente avere una variabile per memorizzare quali funzioni hanno eseguito. Il codice seguente dovrebbe eseguire "runOnce" e "runAgain" entrambi una volta. È ancora booleano, ma sembra che tu non voglia molte variabili.

var runFunctions = {}; 

function runOnce() { 
    if(!hasRun(arguments.callee)) { 
    /* do stuff here */ 
    console.log("once"); 
    } 
} 

function runAgain() { 
    if(!hasRun(arguments.callee)) { 
    /* do stuff here */ 
    console.log("again"); 
    } 
} 


function hasRun(functionName) { 
functionName = functionName.toString(); 
functionName = functionName.substr('function '.length); 
functionName = functionName.substr(0, functionName.indexOf('(')); 

if(runFunctions[functionName]) { 
    return true; 
} else { 
    runFunctions[functionName] = true; 
    return false; 
} 
} 

runOnce(); 
runAgain(); 
runAgain(); 
+1

(Nota: mi piace ancora l'implementazione di Lekensteyn) –

+0

Tre righe per l'analisi del nome della funzione sono piuttosto pesanti. Raccomando una semplice soluzione regexp: 'functionName = func.toString(). Match (/ \ w + (? = \() /) [0]' – bennedich

+0

@Mike Robinson - Perché esistono due definizioni di funzione per 'runAgain()' ? – irishbuzz

6

E una funzione anonima richiamata immediatamente?

(function() { 

    // code in here to run once 

})(); 

il codice verrà eseguito immediatamente e non lascerà traccia nello spazio dei nomi globale.

Se è necessario chiamare questo codice da un'altra posizione, è possibile utilizzare una chiusura per garantire che il contenuto di una funzione venga eseguito una sola volta. Personalmente, preferisco questo a una funzione che si riscrive da solo, poiché ritengo che ciò possa causare confusione, ma a ciascuno di essi :) Questa particolare implementazione sfrutta il fatto che 0 è un valore di falsità.

var once = (function() { 
    var hasRun = 0; 
    return function() { 
    if (!hasRun) { 
     hasRun++; 

     // body to run only once 

     // log to the console for a test  
     console.log("only ran once"); 
    }    
    } 
})(); 

// test that the body of the function executes only once 
for (var i = 0; i < 5; i++) 
    once(); 
0

Inoltre, la natura di ciò che accade nel "/ * fare cose qui * /" può lasciare qualcosa in giro che, quando presente, deve significare che la funzione è eseguito per esempio

var counter = null; 

function initCounter() { 
    if (counter === null) { 
    counter = 0; 
    } 
} 
8

Un problema con un bel po 'di questi approcci è che essi dipendono da nomi di funzione al lavoro: l'approccio di Mike non riuscirà se si crea una funzione con "x = function() ..." e l'approccio di Lekensteyn lo faranno fallire se si imposta x = useThisFunctionOnce prima che venga chiamata useThisFunctionOnce.

mi consiglia di utilizzare l'approccio di chiusura di Russ se si vuole correre subito o l'approccio adottato dalla Underscore.js se si vuole ritardare l'esecuzione:

function once(func) { 
    var ran = false, memo; 
    return function() { 
     if (ran) return memo; 
     ran = true; 
     return memo = func.apply(this, arguments); 
    }; 
} 

var myFunction = once(function() { 
    return new Date().toString(); 
}); 

setInterval(function() {console.log(myFunction());}, 1000); 

Alla prima esecuzione, viene eseguita la funzione interna e la i risultati vengono restituiti.Nelle esecuzioni successive, viene restituito l'oggetto risultato originale.

+0

Ho cercato di capire 'Underscore''s' once' e sono confuso. Alla seconda chiamata, non viene eseguito '' re' re-dichiarato e inizializzato su 'false', chiamando quindi' func.apply() 'una seconda volta? – seebiscuit

+0

@Seabiscuit restituisce una volta la funzione interna e 'run' è stato definito nella funzione esterna. Il risultato è una chiusura in cui esecuzione e memo sono condivisi da tutte le chiamate alla funzione. –

+0

Ho immaginato che fosse così. Quello che ancora non capisco è come (e qui sto ammettendo la mia ignoranza su come le chiusure funzionano a un livello profondo) quando la chiamata è fatta a 'once (func)' l'assegnazione della variabile nella funzione esterna, cioè 'ran = false' non influenza il valore di 'ran' nella funzione' return'. Poiché ogni chiamata a 'once (func)' richiama le funzioni esterne e interne, allora 'ran = false' viene assegnato ogni volta che viene chiamato' once (func) '. – seebiscuit

1
(function(){ 

    var run = (function(){ 

    var func, blank = function() {}; 

    func = function() { 
     func = blank; 

     // following code executes only once 
     console.log('run once !'); 
    }; 

    return function(){ 
     func.call(); 
    }; 
    })(); 

    run(); 
    run(); 
    run(); 
    run(); 

})(); 
3

soluzione elegante da Douglas Crockford, trascorso un po 'di tempo per capire come funziona e imbatterci in questo thread.

Quindi il wrapper restituisce una funzione che richiama semplicemente la funzione del parametro passato. E approfittando delle chiusure questa costruzione ha sostituito la funzione passata alla funzione vuota, o nulla nella fonte originale, dopo la prima chiamata, quindi tutte le chiamate successive saranno inutili.

Questo è qualcosa di molto vicino a tutte le altre risposte, ma è un po 'un codice contenente sé stesso e si può usare indipendentemente, il che è buono. Sto ancora cercando di capire tutto il meccanismo di sostituzione, ma praticamente funziona perfettamente.

function once (func) { 

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

} 

function hi(name) { 
    console.log("Hi %s", name); 
} 

sayonce = once(hi); 
sayonce("Vasya"); 
sayonce("Petya"); 

per coloro che sono curiosi ecco jsbin trasformazioni

1

Ho appena incontrato questo problema, e finito per fare qualcosa di simile al seguente:

function runOnce() { 
    if (!this.alreadyRan) { 
     // put all your functionality here 
     console.log('running my function!'); 

     // set a property on the function itself to prevent it being run again 
     this.alreadyRan = true; 
    } 
} 

Questa sfrutta il fatto che Le proprietà Javascript non sono definite per impostazione predefinita.

Problemi correlati