2013-04-10 15 views
8

Ho una serie di funzioni e cerco un modo conciso per chiamare ciascuno in ordine.Chiama ciascuna funzione nell'elenco

fns = [ 
    function a() { console.log('a') }, 
    function b() { console.log('b') }, 
    function c() { console.log('c') }, 
] 

questo funziona:

fns.map(function (f) { f() }) 

e così fa questo:

fns.map(function (f) { Function.call.call(f) }) 

tuttavia questo solleva un TypeError:

fns.map(Function.call.call) 

Perché non funziona esempio quest'ultimo ?

risposta

4

Questo codice semplificato esibisce un problema simile:

var x = Function.call.call; 
x(alert); 

In questo caso, una volta Function.call.call viene chiamato, esso non ricordare il contesto da cui ha origine (cioè Function.call). Per salvare questo contesto, si potrebbe usare questo empia costrutto trucco:

Function.call.bind(Function.call) 

Esso restituisce una nuova funzione per cui il contesto Function.call è destinata a se stesso, salvando così il contesto. È possibile salvare questa espressione in una nuova variabile:

var callFn = Function.call.bind(Function.call); 

Ora, callFn(alert) è identico a alert.call(). Si noti che tutti gli argomenti aggiuntivi saranno passati così com'è, quindi callFn(alert, window) invocherà alert.call(window). La comprensione di questo comportamento è importante nelle situazioni in cui callFn viene chiamato come parte di un callback come Array.forEach, in cui vengono passati tre argomenti in ogni iterazione.

fns.forEach(callFn); 

Nel tuo caso, nessuna delle funzioni all'interno fns utilizzano gli argomenti che sono passati, ma dietro le quinte si chiamano in questo modo:

fns[0].call(0, fns) 

Così this uguale l'indice dell'elemento (ie Number(0)) e arguments[0] è uguale alla serie di funzioni. L'osservatore acuto potrebbe aver notato che il valore dell'elemento cade tra le fessure, sebbene possa ancora essere referenziato usando arguments[0][this] o, in alternativa, arguments.callee (deprecato).

+0

Bingo, il gioco è fatto! Grazie molto. – georg

+0

Buon seguito sugli argomenti, ma perché 'callee'? Cosa c'è di sbagliato con semplicemente 'argomenti [0] [questo]'? – georg

+0

@ thg435 Questo è un buon punto; che funzionerebbe in questo caso :) aggiornato, grazie! –

5
for (var i = 0, len = fns.length; i < len; i++) { 
    fns[i].call(); 
}; 

Ecco il funzionamento fiddle.

Utilizzare Function.prototype.call e Function.prototype.apply come sopra per chiamare una funzione. Con e apply è possibile passare l'ambito di esecuzione e arguments alla chiamata di funzione. Se non avete bisogno di questo, si può semplicemente fare:

for (var i = 0, len = fns.length; i < len; i++) { 
     fns[i](); 
}; 

Circa il tuo codice:

Array.prototype.map è una funzione che prende callback e scope as parameters. Nei primi due esempi, si utilizza una funzione anonima come callback e si chiama il parametro passato ad esso automaticamente da Array.prototype.map. Per espandere, il tuo codice è l'equivalente di questo:

function single(element) { 
    element(); 
}; 
fns.map(single); 

Quindi quanto sopra è completamente corretto. Seguendo lo stesso principio usando Function.call.call, si chiama la funzione f passata come parametro dalla mappa.

Ma nel suo terzo esempio si stanno costringendo una diretta call via Function.call.prototype.call, tuttavia in questo caso, f non viene passato come parametro, il che significa che il vostro Function.call.call tenterà di chiamare undefined, da cui si ottiene il TypeError. Quando si inserisce all'interno di map(), si è NON passando un callback come argomento.

Il call sarà immediatamente. Function.call.call è la stessa cosa di Function.call.call() o Function.call.call(undefined), che verrà immediatamente valutata quando utilizzata come nel terzo esempio.

+3

Probabilmente l'OP conosce già tutto questo. La domanda chiede * perché *, non * come *. – Jon

+0

In realtà stavo scrivendo lo stesso nel frattempo! +1 – Jon

3

Il TypeError si verifica perché Function.prototype.call invoca internamente this (con il contesto e i parametri specificati, ma ciò non è importante nella discussione).

Riscriviamo il lavoro

fns.map(function (f) { Function.call.call(f) }) 

come l'equivalente

fns.map(function (f) { 
    var invoker = Function.call; 
    invoker.call(f); 
}) 

E 'ormai evidente che invoker viene richiamato con f come this. Quando tenta internamente di richiamare this a sua volta la funzione viene chiamata, come previsto.

Ora un'occhiata a questo:

fns.map(Function.call.call); 

e la forma equivalente

fns.map(function (f) { 
    var invoker = Function.call; 
    invoker.call(); 
}) 

Dovrebbe essere ovvio che qui, quando viene invocato invokerthis è undefined; quindi non può essere invocato da solo e questo dà origine allo TypeError.