Se disponi di uno script che utilizza il runtime Rhino e vuoi sfruttare la sintassi e le funzionalità V8, devi eseguire la migrazione dello script alla versione V8.
La maggior parte degli script scritti utilizzando il runtime Rhino può funzionare con un runtime V8 senza regolazioni. Spesso l'unico prerequisito per l'aggiunta della sintassi e delle funzionalità V8 a uno script è l'abilitazione del runtime V8.
Tuttavia, è presente un piccolo insieme di incompatibilità e altre differenze che possono comportare l'errore o un comportamento imprevisto di uno script dopo l'attivazione del runtime V8. Quando esegui la migrazione di uno script per utilizzare V8, devi cercare questi problemi nel progetto di script e correggere quelli trovati.
Procedura di migrazione V8
Per eseguire la migrazione di uno script alla V8, segui questa procedura:
- Abilita il runtime V8 per lo script.
- Esamina attentamente le incompatibilità elencate di seguito. Esamina lo script per determinare se sono presenti incompatibilità; se sono presenti una o più incompatibilità, modifica il codice dello script per rimuovere o evitare il problema.
- Esamina attentamente le altre differenze elencate di seguito. Esamina lo script per determinare se una delle differenze elencate influisce sul comportamento del codice. Modifica lo script per correggere questo comportamento.
- Dopo aver corretto eventuali incompatibilità o altre differenze rilevate, puoi iniziare ad aggiornare il codice in modo da utilizzare la sintassi V8 e altre funzionalità a seconda delle tue esigenze.
- Dopo aver terminato le modifiche al codice, testa a fondo lo script per assicurarti che funzioni come previsto.
- Se lo script è un'app web o un componente aggiuntivo pubblicato, devi creare una nuova versione dello script con gli aggiustamenti V8. Per rendere la versione V8 disponibile agli utenti, devi pubblicare nuovamente lo script con questa versione.
Incompatibilità
Purtroppo, il runtime Apps Script originale basato su Rhino consentiva diversi comportamenti ECMAScript non standard. Poiché V8 è conforme agli standard, questi comportamenti non sono supportati dopo la migrazione. Se questi problemi non vengono corretti, si verificano errori o comportamenti degli script non funzionanti una volta attivato il runtime V8.
Le seguenti sezioni descrivono ciascuno di questi comportamenti e i passaggi da seguire per correggere il codice dello script durante la migrazione alla V8.
Evita for each(variable in object)
L'istruzione for each (variable in object)
è stata aggiunta a JavaScript 1.6 e rimossa a favore di for...of
.
Quando esegui la migrazione dello script alla V8, evita di utilizzare le istruzioni for each (variable in object)
.
Usa invece for (variable in object)
:
// Rhino runtime var obj = {a: 1, b: 2, c: 3}; // Don't use 'for each' in V8 for each (var value in obj) { Logger.log("value = %s", value); } |
// V8 runtime var obj = {a: 1, b: 2, c: 3}; for (var key in obj) { // OK in V8 var value = obj[key]; Logger.log("value = %s", value); } |
Evita Date.prototype.getYear()
Nel runtime Rhino originale,
Date.prototype.getYear()
restituisce anni a due cifre per gli anni dal 1900 al 1999, ma anni a quattro cifre per altre
date, come era il comportamento in JavaScript 1.2 e versioni precedenti.
Nel runtime V8, Date.prototype.getYear()
restituisce invece l'anno meno 1900, come richiesto dagli standard ECMAScript.
Quando esegui la migrazione dello script alla V8, utilizza sempre Date.prototype.getFullYear()
, che restituisce un anno di quattro cifre indipendentemente dalla data.
Evita di utilizzare parole chiave riservate come nomi
ECMAScript vieta l'uso di determinate parole chiave riservate nei nomi di funzioni e variabili. Il runtime Rhino consentiva molte di queste parole, quindi, se il tuo codice le utilizza, devi rinominare le funzioni o le variabili.
Quando esegui la migrazione dello script alla V8, evita di denominare variabili o funzioni
utilizzando una delle
parole chiave prenotate.
Rinomina qualsiasi variabile o funzione per evitare di utilizzare il nome della parola chiave. Gli utilizzi comuni delle parole chiave come nomi sono class
, import
e export
.
Evita di riassegnare const
variabili
Nel runtime Rhino originale, puoi dichiarare una variabile utilizzando const
, il che significa che il valore del simbolo non cambia mai e le assegnazioni future del simbolo vengono ignorate.
Nel nuovo runtime V8, la parola chiave const
è conforme allo standard e l'assegnazione a una variabile dichiarata come const
genera un errore di runtime TypeError: Assignment to constant variable
.
Quando esegui la migrazione dello script alla V8, non tentare di riassegnare il valore di una variabile const
:
// Rhino runtime const x = 1; x = 2; // No error console.log(x); // Outputs 1 |
// V8 runtime const x = 1; x = 2; // Throws TypeError console.log(x); // Never executed |
Evita i valori letterali XML e l'oggetto XML
Questa estensione non standard di ECMAScript consente ai progetti Apps Script di utilizzare direttamente la sintassi XML.
Quando esegui la migrazione dello script alla V8, evita di utilizzare valori letterali XML diretti o l'oggetto XML.
Utilizza invece XmlService per analizzare il codice XML:
// V8 runtime var incompatibleXml1 = <container><item/></container>; // Don't use var incompatibleXml2 = new XML('<container><item/></container>'); // Don't use var xml3 = XmlService.parse('<container><item/></container>'); // OK |
Non creare funzioni iteratrici personalizzate utilizzando __iterator__
JavaScript 1.7 ha aggiunto una funzionalità per consentire l'aggiunta di un iteratore personalizzato a qualsiasi classe dichiarando una funzione __iterator__
nel prototipo di quella classe. Questa funzionalità è stata aggiunta anche al runtime Rhino di Apps Script per praticità degli sviluppatori. Tuttavia, questa funzionalità non ha mai fatto parte dello standard ECMA-262 ed è stata rimossa nei motori JavaScript conformi a ECMAScript. Gli script che utilizzano V8
non possono utilizzare questa struttura dell'iteratore.
Quando esegui la migrazione dello script alla V8, evita la funzione __iterator__
per creare iteratori personalizzati. Utilizza invece gli iteratori ECMAScript 6.
Considera la seguente struttura dell'array:
// Create a sample array var myArray = ['a', 'b', 'c']; // Add a property to the array myArray.foo = 'bar'; // The default behavior for an array is to return keys of all properties, // including 'foo'. Logger.log("Normal for...in loop:"); for (var item in myArray) { Logger.log(item); // Logs 0, 1, 2, foo } // To only log the array values with `for..in`, a custom iterator can be used. |
I seguenti esempi di codice mostrano come creare un iteratore nel runtime Rhino e come costruire un iteratore sostitutivo nel runtime V8:
// Rhino runtime custom iterator function ArrayIterator(array) { this.array = array; this.currentIndex = 0; } ArrayIterator.prototype.next = function() { if (this.currentIndex >= this.array.length) { throw StopIteration; } return "[" + this.currentIndex + "]=" + this.array[this.currentIndex++]; }; // Direct myArray to use the custom iterator myArray.__iterator__ = function() { return new ArrayIterator(this); } Logger.log("With custom Rhino iterator:"); for (var item in myArray) { // Logs [0]=a, [1]=b, [2]=c Logger.log(item); } |
// V8 runtime (ECMAScript 6) custom iterator myArray[Symbol.iterator] = function() { var currentIndex = 0; var array = this; return { next: function() { if (currentIndex < array.length) { return { value: "[${currentIndex}]=" + array[currentIndex++], done: false}; } else { return {done: true}; } } }; } Logger.log("With V8 custom iterator:"); // Must use for...of since // for...in doesn't expect an iterable. for (var item of myArray) { // Logs [0]=a, [1]=b, [2]=c Logger.log(item); } |
Evita clausole catch condizionali
Il runtime V8 non supporta le clausole catch condizionali catch..if
, in quanto non sono conformi agli standard.
Quando esegui la migrazione dello script alla V8, sposta eventuali condizionali catch all'interno del corpo della catch:
// Rhino runtime try { doSomething(); } catch (e if e instanceof TypeError) { // Don't use // Handle exception } |
// V8 runtime try { doSomething(); } catch (e) { if (e instanceof TypeError) { // Handle exception } } |
Evita di utilizzare Object.prototype.toSource()
JavaScript 1.3 conteneva un metodo Object.prototype.toSource() che non fa mai parte di nessuno standard ECMAScript. Non è supportato nel runtime V8.
Quando esegui la migrazione dello script alla V8, rimuovi qualsiasi utilizzo di Object.prototype.toSource() dal codice.
Altre differenze
Oltre alle incompatibilità precedenti che possono causare errori dello script, esistono alcune altre differenze che, se non corrette, possono causare un comportamento imprevisto dello script di runtime V8.
Le seguenti sezioni spiegano come aggiornare il codice dello script per evitare queste sorprese inaspettate.
Modificare la formattazione di data e ora in base alle impostazioni internazionali
I metodi Date
toLocaleString()
, toLocaleDateString()
e toLocaleTimeString()
si comportano in modo diverso nel runtime V8 rispetto a Rhino.
In Rhino, il formato predefinito è il formato lungo e tutti i parametri trasmessi sono ignorati.
Nel runtime V8, il formato predefinito è il formato breve e i parametri trasmessi sono gestiti in base allo standard ECMA (per informazioni dettagliate, consulta la documentazione di toLocaleDateString()
).
Quando esegui la migrazione dello script alla V8, testa e modifica le aspettative del codice in merito all'output dei metodi di data e ora specifici per le impostazioni internazionali:
// Rhino runtime var event = new Date( Date.UTC(2012, 11, 21, 12)); // Outputs "December 21, 2012" in Rhino console.log(event.toLocaleDateString()); // Also outputs "December 21, 2012", // ignoring the parameters passed in. console.log(event.toLocaleDateString( 'de-DE', { year: 'numeric', month: 'long', day: 'numeric' })); |
// V8 runtime var event = new Date( Date.UTC(2012, 11, 21, 12)); // Outputs "12/21/2012" in V8 console.log(event.toLocaleDateString()); // Outputs "21. Dezember 2012" console.log(event.toLocaleDateString( 'de-DE', { year: 'numeric', month: 'long', day: 'numeric' })); |
Evita di utilizzare Error.fileName
e Error.lineNumber
Nel periodo di inattività della versione V8, l'oggetto JavaScript standard Error
non supporta fileName
o lineNumber
come parametri del costruttore o proprietà dell'oggetto.
Quando esegui la migrazione dello script alla V8, rimuovi qualsiasi dipendenza da Error.fileName
e Error.lineNumber
.
In alternativa, puoi utilizzare Error.prototype.stack
.
Anche questo stack non è standard, ma è supportato sia in Rhino che nella V8. Il formato dell'analisi dello stack prodotta dalle due piattaforme è leggermente diverso:
// Rhino runtime Error.prototype.stack // stack trace format at filename:92 (innerFunction) at filename:97 (outerFunction) |
// V8 runtime Error.prototype.stack // stack trace format Error: error message at innerFunction (filename:92:11) at outerFunction (filename:97:5) |
Modifica la gestione degli oggetti enum con stringhe
Nel runtime Rhino originale, l'utilizzo del metodo JavaScript JSON.stringify()
su un oggetto enum restituisce solo {}
.
In V8, utilizzando lo stesso metodo su un oggetto enum, viene ripristinato il nome enum.
Quando esegui la migrazione dello script alla V8, testa e modifica le aspettative del codice relative all'output di JSON.stringify()
su oggetti enum:
// Rhino runtime var enumName = JSON.stringify(Charts.ChartType.BUBBLE); // enumName evaluates to {} |
// V8 runtime var enumName = JSON.stringify(Charts.ChartType.BUBBLE); // enumName evaluates to "BUBBLE" |
Modifica la gestione dei parametri non definiti
Nel runtime Rhino originale, il passaggio di undefined
a un metodo come parametro
ha comportato il passaggio della stringa "undefined"
a quel metodo.
In V8, passare undefined
ai metodi è equivalente a null
.
Quando esegui la migrazione dello script alla V8, testa e modifica le aspettative del codice relative ai parametri undefined
:
// Rhino runtime SpreadsheetApp.getActiveRange() .setValue(undefined); // The active range now has the string // "undefined" as its value. |
// V8 runtime SpreadsheetApp.getActiveRange() .setValue(undefined); // The active range now has no content, as // setValue(null) removes content from // ranges. |
Modifica la gestione delle this
globali
Il runtime Rhino definisce un contesto speciale implicito per gli script che lo utilizzano.
Il codice di script viene eseguito in questo contesto implicito, diverso dall'effettivo this
globale. Ciò significa che i riferimenti all'elemento "this
globale" nel codice in realtà
valutano il contesto speciale, che contiene solo il codice e le variabili
definiti nello script. I servizi Apps Script e gli oggetti ECMAScript integrati sono esclusi da questo uso di this
. La situazione era simile a questa
struttura JavaScript:
// Rhino runtime // Apps Script built-in services defined here, in the actual global context. var SpreadsheetApp = { openById: function() { ... } getActive: function() { ... } // etc. }; function() { // Implicit special context; all your code goes here. If the global this // is referenced in your code, it only contains elements from this context. // Any global variables you defined. var x = 42; // Your script functions. function myFunction() { ... } // End of your code. }(); |
Nella V8, il contesto speciale implicito viene rimosso. Le variabili e le funzioni globali definite nello script vengono inserite nel contesto globale, accanto ai servizi Apps Script integrati e agli elementi integrati ECMAScript come Math
e Date
.
Quando esegui la migrazione dello script alla V8, testa e modifica le aspettative del codice relative all'uso di this
in un contesto globale. Nella maggior parte dei casi, le differenze sono evidenti solo se il codice esamina le chiavi o i nomi delle proprietà dell'oggetto this
globale:
// Rhino runtime var myGlobal = 5; function myFunction() { // Only logs [myFunction, myGlobal]; console.log(Object.keys(this)); // Only logs [myFunction, myGlobal]; console.log( Object.getOwnPropertyNames(this)); } |
// V8 runtime var myGlobal = 5; function myFunction() { // Logs an array that includes the names // of Apps Script services // (CalendarApp, GmailApp, etc.) in // addition to myFunction and myGlobal. console.log(Object.keys(this)); // Logs an array that includes the same // values as above, and also includes // ECMAScript built-ins like Math, Date, // and Object. console.log( Object.getOwnPropertyNames(this)); } |
Modifica la gestione di instanceof
nelle librerie
L'utilizzo di instanceof
in una libreria su un oggetto passato come parametro in una funzione di un altro progetto può generare falsi negativi. Nel runtime V8, un progetto e le sue librerie vengono eseguiti in diversi contesti di esecuzione e quindi hanno catene globali e prototipi diversi.
Tieni presente che questo avviene solo se la libreria utilizza instanceof
su un oggetto non creato nel tuo progetto. L'utilizzo su un oggetto creato nel progetto, nello stesso script o in uno diverso all'interno del progetto, dovrebbe funzionare come previsto.
Se un progetto in esecuzione sulla V8 utilizza lo script come libreria, verifica se lo script utilizza instanceof
su un parametro che verrà trasmesso da un altro progetto. Modifica l'utilizzo di instanceof
e usa altre alternative attuabili in base al tuo caso d'uso.
Un'alternativa a a instanceof b
può essere utilizzare il costruttore di a
nei casi in cui non sia necessario cercare l'intera catena di prototipi e semplicemente controllare il costruttore.
Utilizzo: a.constructor.name == "b"
Considera il Progetto A e il Progetto B, dove il Progetto A utilizza il Progetto B come libreria.
//Rhino runtime //Project A function caller() { var date = new Date(); // Returns true return B.callee(date); } //Project B function callee(date) { // Returns true return(date instanceof Date); } |
//V8 runtime //Project A function caller() { var date = new Date(); // Returns false return B.callee(date); } //Project B function callee(date) { // Incorrectly returns false return(date instanceof Date); // Consider using return (date.constructor.name == // “Date”) instead. // return (date.constructor.name == “Date”) -> Returns // true } |
Un'altra alternativa può essere introdurre una funzione che controlla instanceof
nel progetto principale e passare la funzione oltre ad altri parametri quando chiami una funzione della libreria. La funzione passata
può essere utilizzata per controllare instanceof
all'interno della libreria.
//V8 runtime //Project A function caller() { var date = new Date(); // Returns True return B.callee(date, date => date instanceof Date); } //Project B function callee(date, checkInstanceOf) { // Returns True return checkInstanceOf(date); } |
Regola il passaggio di risorse non condivise alle librerie
Il passaggio di una risorsa non condivisa dallo script principale a una libreria funziona in modo diverso nel runtime V8.
Nel runtime di Rhino, non è possibile passare una risorsa non condivisa. La libreria utilizza invece la propria risorsa.
Nel runtime V8, è possibile passare una risorsa non condivisa alla libreria. La libreria utilizza la risorsa non condivisa passata.
Non passare risorse non condivise come parametri di funzione. Dichiara sempre le risorse non condivise nello stesso script in cui vengono utilizzate.
Considera il Progetto A e il Progetto B, dove il Progetto A utilizza il Progetto B come libreria. In questo esempio, PropertiesService
è una risorsa non condivisa.
// Rhino runtime // Project A function testPassingNonSharedProperties() { PropertiesService.getScriptProperties() .setProperty('project', 'Project-A'); B.setScriptProperties(); // Prints: Project-B Logger.log(B.getScriptProperties( PropertiesService, 'project')); } |
// V8 runtime // Project A function testPassingNonSharedProperties() { PropertiesService.getScriptProperties() .setProperty('project', 'Project-A'); B.setScriptProperties(); // Prints: Project-A Logger.log(B.getScriptProperties( PropertiesService, 'project')); } |
Aggiorna l'accesso agli script autonomi
Per gli script autonomi in esecuzione sul runtime V8, devi fornire agli utenti almeno l'accesso in visualizzazione allo script affinché i trigger dello script funzionino correttamente.