12

Molti di voi hanno probabilmente familiarità con questo piccolo codice di monitoraggio offerto da Google Analytics.Come ridurre JavaScript come Google Analytics?

<script> 
(
    function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ 
    (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), 
    m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) 
    } 
)(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); 

ga('create', 'UA-00000000-0', 'auto'); 
ga('send', 'pageview'); 
</script> 

La parte interessante è che questo piccolo estratto contiene argomenti che formano la parola isogramma. Questo script usa anche argomenti per dichiarare le variabili per rimuovere alcuni bit dalla dimensione finale del file. Probabilmente, non useresti questo schema quando scrivi il codice (?), Quindi la mia domanda è: in che modo Google minimizza il suo codice e queste tecniche sono disponibili anche per i comuni mortali?

Ho trovato online questo example di Stephen Morley che include un codice che sembra qualcosa che avresti scritto prima di ridimensionarlo. Ho preso questo codice e l'ho eseguito tramite lo stesso Closure Compiler di Google sull'ottimizzazione avanzata. Come previsto, il codice risultante non assomiglia allo script effettivo utilizzato da Google Analytics.

(function(){window.b="ga";"ga"in window||(window.a=function(){window.a.q.push(arguments)},window.a.q=[]);window.a.c=(new Date).getTime();var c=document.createElement("script");c.src="//www.google-analytics.com/analytics.js";c.async=!0;var d=document.getElementsByTagName("script")[0];d.parentNode.insertBefore(c,d)})(); 

Questa volta, il codice è meno ASCIUTTO e più grande, anche senza i due comandi aggiuntivi.

Quindi, per chiarire, sono curioso di come gli ingegneri di Google siano arrivati ​​al risultato di cui sopra (non credo che il loro codice assomigli effettivamente a quello dell'esempio di Stephen) e questo processo potrebbe essere replicato anche se non si fa parte di Google? Grazie in anticipo!

+3

Non so, ma è uno script così minuscolo, potrebbe facilmente essere fatto a mano usando i nomi dei parametri di funzione desiderati. –

+2

... ciò che è interessante è che lo script 'ga' usa la notazione della parentesi con una stringa letterale:' i ['GoogleAnalyticsObject'] ', che i minificatori (incluso Closure Compiler) di solito convertono in sintassi a punti, quindi questo mi fa pensare probabilmente è fatto a mano, con le parentesi usate solo nel caso in cui funzioni attraverso CC, quindi non lo trasforma in 'window.b =" ga "'. –

+0

@squint Grazie! Sì, sembra che sia stato fatto a mano, mi stavo chiedendo se non mi manca qualcosa – lmenus

risposta

2

Ho la sensazione che la parola "isogramma" sia un po 'un suggerimento astuto dal dipendente di Google che ha minimizzato questo codice.

Poiché un isogramma è a word with no repeating characters, rappresenta la logica precisa necessaria per la minimizzazione di parametri e altri nomi di variabili che devono essere univoci l'uno dall'altro.

Con tutta probabilità, questo termine è stato inserito nel minificatore, in modo che la prima raccolta di variabili minificate indichi che conoscono un po 'la logica del sequenziamento di lettere univoche.

Poiché la parola isogram è di per sé un isogramma, la persona che ha creato la logica di minificazione potrebbe impostarlo per controllare un parametro o un elenco di argomenti per un caso in cui sono presenti 7 argomenti/parametri e in tal caso è sufficiente sostituirli con la lettera corrispondente nella parola "isogramma". Ciò comporterebbe un sovraccarico, ma tali casi sono rari e Google ha un sacco di server e ingegneri di rete per ottimizzare i propri script.

3

In realtà è abbastanza semplice e un compito divertente scrivere tali script.

Ecco un lungo esempio come trasformare una normale funzione in qualcosa di simile a questo:

mi piacerebbe iniziare con uno script immaginario. Ho incluso uno scriptLoader che carica un file javascript in modo asincrono:

window.loadScript = function(src){ 
    const scriptTag = document.createElement('script'); 
    scriptTag.async = true; 
    scriptTag.src = src; 

    const anyOtherScriptTag = document.getElementsByTagName('script')[0]; 
    anyOtherScriptTag.parentNode.insertBefore(scriptTag, anyOtherScriptTag); 
} 

Quando chiamato in questo modo: loadScript("/url.js") che verrà inserire un nuovo tag script (prima del primo tag script) nel DOM e il browser scaricherà lo script .

Fin qui tutto bene. Diciamo che voglio passare questo argomento di script prima che sia caricato. All'interno dello script che verrà caricato accedo a un oggetto globale univoco. Chiamiamolo window.myScriptArgs. Quindi idealmente, una volta caricato lo script, legge window.myScriptArgs ed esegue di conseguenza.

Ora ho potuto fare window.myScriptArgs = [] e chiamare un giorno, ma dato che il mio esempio ipotetico servirà solo a caricare un file di script singolo, aggiungo la logica alla funzione CaricaScript pure.

window.loadScript = function(src){ 
    window.myScriptArgs = window.myScriptArgs || []; 
    const scriptTag = document.createElement('script'); 
    scriptTag.async = true; 
    scriptTag.src = src; 

    const anyOtherScriptTag = document.getElementsByTagName('script')[0]; 
    anyOtherScriptTag.parentNode.insertBefore(scriptTag, anyOtherScriptTag); 
} 
loadScript("/my-script.js"); 

Ok, controllo se myScriptArgs è già presente e se non impostato a un array vuoto. Ora so anche che my-script.js espone un metodo globale myScript(). Quindi scrivo uno stub per questo. Questo stub metterà ogni argomento ha ricevuto ad esso nella matrice myScriptArgs:

window.myScript =() => { 
    window.myScriptArgs = window.myScriptArgs || []; 
    window.myScriptArgs.push(arguments); 
} 

ora posso chiamare CaricaScript e chiamare immediatamente myScript() con determinati argomenti. Non è necessario preoccuparsi di problemi di caricamento o quant'altro. Una volta caricato "my-script.js", viene letto window.myScriptArgs e funziona come escluso. Il codice è simile al seguente:

window.myScript =() => { 
    window.myScriptArgs = window.myScriptArgs || []; 
    window.myScriptArgs.push(arguments); 
} 

window.loadScript = function(src){ 
    window.myScriptArgs = window.myScriptArgs || []; 
    const scriptTag = document.createElement('script'); 
    scriptTag.async = true; 
    scriptTag.src = src; 

    const anyOtherScriptTag = document.getElementsByTagName('script')[0]; 
    anyOtherScriptTag.parentNode.insertBefore(scriptTag, anyOtherScriptTag); 
} 
loadScript("/my-script.js"); 
myScript('command', 'args', 'args1'); 
myScript('command2', 'args3', 'args4'); 

Ok, funziona come previsto.Cerchiamo di ottimizzarlo Per prima cosa ho combinare lo stub loadScript e myScript ad una singola funzione chiamata initMyScript():

window.initMyScript = function(src){ 
    window.myScriptArgs = window.myScriptArgs || []; 
    window.myScript = window.myScript || function(){ 
     window.myScriptArgs.push(arguments); 
    } 

    const scriptTag = document.createElement('script'); 
    scriptTag.async = true; 
    scriptTag.src = src; 

    const anyOtherScriptTag = document.getElementsByTagName('script')[0]; 
    anyOtherScriptTag.parentNode.insertBefore(scriptTag, anyOtherScriptTag); 
} 
initMyScript("/my-script.js"); 
myScript('command', 'args', 'args1'); 
myScript('command2', 'args3', 'args4'); 

Non è niente di troppo atm fantasia. Ora ho intenzione di sbarazzarmi delle chiamate multiple window. passando window come argomento a initMyScript. Lo farò anche con document.

Lo script si presenta così:

window.initMyScript = function(p, a, src){ 
    p.myScriptArgs = p.myScriptArgs || []; 
    p.myScript = p.myScript || function(){ 
     p.myScriptArgs.push(arguments); 
    } 

    const scriptTag = a.createElement('script'); 
    scriptTag.async = true; 
    scriptTag.src = src; 

    const anyOtherScriptTag = a.getElementsByTagName('script')[0]; 
    anyOtherScriptTag.parentNode.insertBefore(scriptTag, anyOtherScriptTag); 
} 
initMyScript(window, document, "/my-script.js"); 

Ora vediamo dove mi ripeto a me stesso di risparmiare un po 'più bit. Io uso la stringa script due volte, lo stesso per myScript:

window.initMyScript = function(p, a, s, c, src){ 
    p.myScriptArgs = p.myScriptArgs || []; 
    p[c] = p[c] || function(){ 
     p.myScriptArgs.push(arguments); 
    } 

    const scriptTag = a.createElement(s); 
    scriptTag.async = true; 
    scriptTag.src = src; 

    const anyOtherScriptTag = a.getElementsByTagName(s)[0]; 
    anyOtherScriptTag.parentNode.insertBefore(scriptTag, anyOtherScriptTag); 
} 
initMyScript(window, document, 'script', 'myScript', "/my-script.js"); 

Il prossimo passo nel mio viaggio è quello di rendere le variabili breve. E ho anche disattivare questa funzione in una funzione self-executing per salvare la definizione window.initMyScript:

(function(p, a, s, c, src){ 
    p.myScriptArgs = p.myScriptArgs || []; 
    p[c] = p[c] || function(){ 
     p.myScriptArgs.push(arguments); 
    } 

    const q = a.createElement(s); 
    q.async = true; 
    q.src = src; 

    const d = a.getElementsByTagName(s)[0]; 
    d.parentNode.insertBefore(q, d); 
})(window, document, 'script', 'myScript', "/my-script.js"); 

e al mio ultimo mistero: modifico i parametri della funzione per confondere la gente e anche minify il codice ancora di più. Puoi effettivamente concatenare le funzioni in javascript usando le virgole;).

(function(p, a, s, c, A, l, i){ 
    p["myScriptArgs"]=p["myScriptArgs"]||[],p[c] = p[c]||function(){ 
     p["myScriptArgs"].push(arguments)}, 
    l = a.createElement(s);l.async = true;l[A] = A; 
    i = a.getElementsByTagName(s)[0]; 
    i.parentNode.insertBefore(l, i); 
})(window, document, 'script', 'myScript', "/my-script.js"); 
myScript("arg1", "arg2"); 
myScript("arg2", "arg3"); 

notano che aggiungo due parametri supplementari in funzione, è perché ho bisogno di salvare l'elemento restituito da createElement e non voglio utilizzare un'istruzione var;).

Puoi fare ancora di più, ma ottieni il punto. Per piccole funzioni, puoi farlo da solo senza problemi.

Inoltre, è possibile utilizzare un minifier come UglifyJS e quindi rinominare le variabili da soli dopo se siete veramente in che cosa intera isogram ...

Nota: Non ho prove di tutto questo codice. Ecco i draghi. Il codice immaginario è il mio cattivo tentativo di de-offuscare l'esempio di Google. Lo snippet di google-analytics funziona quasi come il mio snippet personalizzato. GA ottimizza un po 'di più (ad esempio trasformando true in 1) ma ottieni il punto.

Per saperne di più le cose utilizzate nel mio esempio: Immediately Invoked Function Expression Property accessors (especially Bracket notation)

e JavaScript cose specifiche come passare tre argomenti a una funzione che prende 5.

+0

Non credo che questa risposta sia pertinente alla domanda che riguarda efficacemente come ottenere una tale minimizzazione che dovrebbe contenere una parola reale "i, s, o, g, r, a, m" di nomi di argomenti piuttosto che come caricare JS in modo asincrono. E la tua risposta a questa domanda sembra essere "fallo a mano" che non fornisce ulteriori informazioni. – SergGr

+0

Ben diverso dal ritocco di uglifyJS o di qualsiasi altro minificatore, non è possibile ottenere l'output desiderato se non eseguendolo manualmente. –

4

Google è bello, perché ci danno una documentazione completa su molte cose su https://developers.google.com

Così molte delle sue risposte si possono trovare su:

Ecco la unminified analytics.js

(function(i, s, o, g, r, a, m){ 
    i['GoogleAnalyticsObject'] = r; // Acts as a pointer to support renaming. 

    // Creates an initial ga() function. 
    // The queued commands will be executed once analytics.js loads. 
    i[r] = i[r] || function() { 
    (i[r].q = i[r].q || []).push(arguments) 
    }, 

    // Sets the time (as an integer) this tag was executed. 
    // Used for timing hits. 
    i[r].l = 1 * new Date(); 

    // Insert the script tag asynchronously. 
    // Inserts above current tag to prevent blocking in addition to using the 
    // async attribute. 
    a = s.createElement(o), 
    m = s.getElementsByTagName(o)[0]; 
    a.async = 1; 
    a.src = g; 
    m.parentNode.insertBefore(a, m) 
})(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga'); 

// Creates a default tracker with automatic cookie domain configuration. 
ga('create', 'UA-XXXXX-Y', 'auto'); 

// Sends a pageview hit from the tracker just created. 
ga('send', 'pageview'); 

E qui è la versione minified essi forniscono (abbastanza versione):

(function (i, s, o, g, r, a, m) { 
    i['GoogleAnalyticsObject'] = r; 
    i[r] = i[r] || function() { 
      (i[r].q = i[r].q || []).push(arguments) 
     }, i[r].l = 1 * new Date(); 
    a = s.createElement(o), 
     m = s.getElementsByTagName(o)[0]; 
    a.async = 1; 
    a.src = g; 
    m.parentNode.insertBefore(a, m) 
})(window, document, 'script', 'https://www.google-analytics.com/analytics.js', 'ga'); 

ga('create', 'UA-XXXXX-Y', 'auto'); 
ga('send', 'pageview'); 

E here la versione minified con gli strumenti di chiusura del compilatore

(function (a, e, f, g, b, c, d) { 
    a.GoogleAnalyticsObject = b; 
    a[b] = a[b] || function() {(a[b].q = a[b].q || []).push(arguments)}; 
    a[b].l = 1 * new Date; 
    c = e.createElement(f); 
    d = e.getElementsByTagName(f)[0]; 
    c.async = 1; 
    c.src = g; 
    d.parentNode.insertBefore(c, d) 
})(window, document, "script", "//www.google-analytics.com/analytics.js", "ga"); 
ga("create", "UA-XXXXX-Y", "auto"); 
ga("send", "pageview"); 

Sembra uguale.
È possibile trovare ulteriori dettagli sul progetto su Github repository.

2

Mi piace pensare che ci siano infiniti modi possibili per scrivere codice. (anche se questo potrebbe non essere vero) Un modo per scrivere il codice dove è limitato e possibilmente risparmiare spazio è usare l'offuscamento. Ad esempio, il seguente codice:

function NewObject(prefix) 
{ 
    var count=0; 
    this.SayHello=function(msg) 
    { 
      count++; 
      alert(prefix+msg); 
    } 
    this.GetCount=function() 
    { 
      return count; 
    } 
} 
var obj=new NewObject("Message : "); 
obj.SayHello("You are welcome."); 

Può essere offuscato per assomigliare a questo:

var _0x3c28=["\x53\x61\x79\x48\x65\x6C\x6C\x6F","\x47\x65\x74\x43\x6F\x75\x6E\x74","\x4D\x65\x73\x73\x61\x67\x65\x20\x3A\x20","\x59\x6F\x75\x20\x61\x72\x65\x20\x77\x65\x6C\x63\x6F\x6D\x65\x2E"];function NewObject(_0x12c4x2){var _0x12c4x3=0;this[_0x3c28[0]]= function(_0x12c4x4){_0x12c4x3++;alert(_0x12c4x2+ _0x12c4x4)};this[_0x3c28[1]]= function(){return _0x12c4x3}}var obj= new NewObject(_0x3c28[2]);obj.SayHello(_0x3c28[3]) 

Ciò è stato fatto utilizzando un algoritmo di offuscamento gratuito su https://javascriptobfuscator.com/Javascript-Obfuscator.aspx.

Sono sicuro che Google ha i propri modi di gestire il codice, ovviamente :).

1

immagine per avere un pezzo di codice come segue:

(function(){ 
    window.GoogleAnalyticsObject = 'ga'; 
    window.ga = window.ga || function(){ 
    (window.ga.q = window.ga.q || []).push(arguments) 
    }, 
    window.ga.l =1 * new Date(); 
    var a = document.createElement('script'), 
    var m = document.getElementsByTagName('script')[0]; 
    a.async = 1; 
    a.src = '//www.google-analytics.com/analytics.js'; 
    m.parentNode.insertBefore(a, m) 
})(); 

Quindi, modificare il codice in modo da passare tutto l'oggetto necessario come parametri:

(function(i, s, o, g, r, a, m){ 
    i['GoogleAnalyticsObject'] = r; 
    i[r] = i[r] || function(){ 
    (i[r].q = i[r].q || []).push(arguments) 
    }, 
    i[r].l =1 * new Date(); 
    a = s.createElement(o), 
    m = s.getElementsByTagName(o)[0]; 
    a.async = 1; 
    a.src = g; 
    m.parentNode.insertBefore(a, m) 
})(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga'); 

Rimuovere tutti gli spazi e finalmente ottieni:

(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ 
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), 
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) 
})(window,document,'script','//www.google-analytics.com/analytics.js','ga'); 

Spero di essere stato chiaro, ciao.

Aggiornamento: Stai chiedendo perché scelgono la parola "isogramma"? È una delle parole "note" dell'isolato, vedi Wikipedia se hai bisogno di più parametri.

-1

È possibile utilizzare npm e un task runner come gulp. Gulp ha un plugin chiamato uglify che elimina spazi extra e prende parametri e variabili e li riduce a una lettera per ridurre ulteriormente il numero complessivo di caratteri nel codice.