Ciao, mi pare siano due soluzioni non comparabili per un unico specifico problema. Provo a fare un po' di chiarezza; come tu hai affermato, si tratta di due metodi usati generalmente per scopi differenti.

funzioni anonime di self-invoking che vengono eseguite immediatamente non appena vengono rilevate da JavaScript e document.ready dove la funzione viene eseguita solo quando il documento (DOM) è completamente caricato e sicuro da manipolare
C'è però da aggiungere che il primo caso (con riferimento al codice da te riportato) non solo è una funzione self-invoking ma si tratta, nello specifico, di una tecnica usata per evitare conflitti con altre librerie che usano la stessa variabile ($) come oggetto principale. Tant'è che nell'argomento della funzione viene passato il riferimento all'oggetto jQuery; per cui, dentro tale funzione, si è sicuri che la variabile $ (argomento della funzione) corrisponda a jQuery e non ad altre librerie eventualmente caricate nello stesso documento.

Qui un riferimento: Avoiding Conflicts with Other Libraries

Il secondo caso, come tu stesso hai indicato, serve per avere certezza che gli elementi del DOM siano stati creati, dopo l'apertura della pagina, e quindi manipolabili da script.

L'uso di uno o dell'altro metodo va esaminato in base allo specifico contesto in cui vuoi che sia eseguito il relativo script.

Però, da ciò che chiedi nelle ultime domande, mi viene da pensare (scusami se sbaglio) che non ti sia ben chiaro il concetto di "elementi del DOM creati e disponibili per la manipolazione".

Apro una parentesi; tu chiedi infatti:
Se in una pagina gli elementi DOM sono già definiti nel codice HTML è meglio utilizzare l'anonymous self-invoking
ma mi lascia perplesso l'uso della frase "sono già definiti nel codice HTML" come se tu stia dando per scontato che il semplice codice HTML, della pagina che si sta aprendo, sia sufficiente per determinare che gli elementi (relativi chiaramente a quel codice) siano disponibili e manipolabili; non è esattamente così - ma forse ho inteso male io ciò che hai indicato.

Ancora, scrivi:
gli elementi del DOM sono creati dinamicamente durante il caricamento
ma in questo caso il termine "dinamicamente" mi pare contrasti con quello di "caricamento". In generale, durante il caricamento del documento (cioè della pagina HTML che si va ad aprire), gli elementi sono creati, per così dire, in modo "statico", cioè quelli vengono "letti" (durante il parsing) e quelli vengono creati; mentre con "elementi creati dinamicamente" in genere ci si riferisce a quegli elementi creati in modo "asincrono" cioè in momenti non determinati, dopo che il documento è già bello che caricato. In questo caso il ready stesso da solo non può assicurarti che gli elementi siano disponibili per la manipolazione - ma magari ho ancora inteso male io.

Forse (correggimi se sbaglio) il tuo dubbio nasce proprio da un'errata concezione di queste nozioni.

Cerco di chiarire velocemente. Il codice HTML è un semplice markup che viene dato in pasto al browser, ma il documento in sé non possiede alcun oggetto, funzione, metodo, o chissà ché - è semplicemente una "zuppa di tag".

Quando si apre il documento HTML, quindi il browser riceve questa "zuppa", avvengono diversi passaggi (riassumo a grandi linee):

- il browser legge questo codice; in questo punto non è stato ancora creato il DOM (modello a oggetti del documento) e nessun elemento è ancora disponibile, ne tanto meno gli script sono eseguiti;

- avviene il parsing (cioè una interpretazione del codice) e quindi...

- viene costruita la mappa DOM (albero strutturato degli oggetti)

- infine vengono creati gli elementi leggendo il DOM nel verso in cui è stato creato (esattamente nel verso in cui viene letto e interpretato il codice HTML, dall'alto verso il basso)

- in quest'ultimo passaggio tutti gli elementi (compresi gli script) vengono creati (leggendoli dall'alto verso il basso) e sono resi disponibili (e quindi manipolabili da script) man mano che ciascuno di essi è creato.

Nota bene che anche gli elementi script vengono creati nello stesso modo di tutti gli altri elementi della pagina. Questi sono quindi eseguiti singolarmente, cioè ciascuno script (qualora sia frammentato in più parti nel documento) è letto, interpretato, ed eseguito nel momento in cui viene creato, prima di proseguire con la creazione degli elementi successivi.

Va da sé quindi che gli elementi non sono disponibili tutti nel momento in cui è semplicemente letto il codice HTML, ma questi vengono resi disponibili durante il processo di parsing. Gli script, anch'essi letti ed eseguiti man mano durante questo processo, nel momento in cui sono eseguiti (che siano script fuori da funzioni oppure dentro a funzioni eseguite in quell'istante) possono quindi far riferimento solo esclusivamente agli elementi che li precedono, cioè a quegli elementi che vengono creati prima che lo script stesso sia eseguito.

Per concludere: un semplice script posto alla fine del body, viene eseguito dopo la creazione di tutti gli elementi della pagina (dando per scontato che dopo il body stesso non sia presente nessun altro elemento). In tal caso tutti gli elementi, che sono manipolati da questo script, saranno certamente disponibili.

Lo script presente in una IIFE (la tua self-invoking) sarà eseguito comunque all'istante in cui è letto, per cui, se questo fa diretto riferimento ad elementi del DOM, devi assicurarti che tali elementi siano disponibili.

Il metodo ready di jQuery viene proposto proprio per risolvere questo problema, cioè per eseguire il codice anche se posto, ad esempio, nell'head della pagina HTML (cioè prima che il body e tutti gli elementi in esso presenti siano stati creati). E' simile all'evento load del body, per cui (a grandi linee), il callback con tutto lo script che gli sta dentro, viene eseguito solo dopo che la fase di parsing è terminata, con la certezza che tutti gli elementi (dell'HTML appena esaminato) sono disponibili sulla pagina e quindi manipolabili da script.

Per rispondere alle tue domande, devi considerare i concetti appena accennati:

- puoi eseguire qualsiasi script che faccia riferimento ad elementi del DOM (che sia fuori da funzioni o dentro IIFE) solo se questi sono stati già creati e disponibili per la manipolazione;

- puoi usare una IIFE se ti serve mantenere un ambito locale per le variabili utilizzate nello script stesso, dal momento che si tratta comunque di una funzione;

- puoi usare infatti una IIFE, passando l'oggetto principale jQuery come riferimento, per situazioni in cui possono presentarsi conflitti tra librerie;

- puoi usare il metodo ready di jQuery in qualunque punto del documento, per avere certezza che lo script al suo interno sia eseguito solo dopo la creazione degli elementi DOM (relativi alla pagina appena aperta);

- per situazioni di conflitto puoi inoltre usare il metodo ready ma col riferimento a jQuery che sarà passato come argomento del callback (vedi: Use the Argument That's Passed to the jQuery( document ).ready() Function)

Ancora, se ti serve manipolare all'istante determinati elementi, può essere più "rapido" (in termini di esecuzione, rispetto al ready) inserire il tuo script (ovviamente tra <script></script>) subito dopo i tag relativi agli elementi da manipolare. E' evidente che lo script sarà eseguito subito senza attendere che il processo di parsing sia concluso, come invece avverrebbe per il ready.

La scelta di una delle varie soluzioni andrà valutata a seconda di cosa ti serve ottenere nello specifico, avendo ovviamente chiari anche i concetti appena esposti.

A mio parere non è comunque possibile determinare "meglio una o meglio l'altra" senza conoscere il contesto, le varie peculiarità e le tue esigenze.