Migrazione degli script al runtime V8

Se disponi già di uno script che utilizza il runtime di Rhino e desideri utilizzare la sintassi e le funzionalità di V8, devi eseguire la migrazione dello script a V8.

La maggior parte degli script scritti con il runtime Rhino può funzionare con un runtime V8 senza necessità di regolazioni. Spesso l'unico prerequisito per l'aggiunta della sintassi e delle funzionalità V8 a uno script è l'attivazione del runtime V8.

Tuttavia, esiste un piccolo insieme di incompatibilità e altre differenze che possono causare un errore o un comportamento imprevisto dello script dopo l'attivazione del runtime V8. Quando esegui la migrazione di uno script per utilizzare V8, devi cercare questi problemi nel progetto dello script e correggere quelli trovati.

Procedura di migrazione V8

Per eseguire la migrazione di uno script alla versione V8, segui questa procedura:

  1. Abilita il runtime V8 per lo script.
  2. Esamina attentamente le incompatibilità elencate di seguito. Esamina lo script per determinare se sono presenti incompatibilità; nel caso siano presenti una o più incompatibilità, modifica il codice dello script per rimuovere o evitare il problema.
  3. 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 il comportamento.
  4. Dopo aver corretto eventuali incompatibilità o altre differenze rilevate, puoi iniziare ad aggiornare il codice per utilizzare la sintassi e altre funzionalità di V8 come preferisci.
  5. Dopo aver terminato le modifiche al codice, testa accuratamente lo script per assicurarti che funzioni come previsto.
  6. Se lo script è un'app web o un componente aggiuntivo pubblicato, devi creare una nuova versione dello script con le modifiche V8. Per rendere la versione V8 disponibile agli utenti, devi ripubblicare 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, vengono generati errori o un comportamento dello script non funzionante dopo l'attivazione del runtime V8.

Le sezioni seguenti descrivono ciascuno di questi comportamenti e i passaggi da seguire per correggere il codice dello script durante la migrazione a 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 a V8, evita di utilizzare 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 originale di Rhino, Date.prototype.getYear() restituisce gli anni a due cifre per gli anni dal 1900 al 1999, ma gli anni a quattro cifre per le altre date, che era il comportamento in JavaScript 1.2 e precedenti.

Nel runtime V8, Date.prototype.getYear() restituisce l'anno meno 1900, come richiesto dagli standard ECMAScript.

Quando esegui la migrazione dello script alla versione 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'utilizzo di alcune parole chiave riservate nei nomi di funzioni e variabili. Il runtime di Rhino ha consentito molte di queste parole, quindi se vengono usate nel codice, devi rinominare le funzioni o le variabili.

Quando esegui la migrazione dello script a V8, evita di nominare 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 di Rhino originale, puoi dichiarare una variabile utilizzando const; in questo modo, 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 a 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 a V8, evita di utilizzare valori letterali XML diretti o l'oggetto XML.

Utilizza invece XmlService per analizzare il file 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 di iteratore 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 è stata aggiunta anche al runtime Rhino di Apps Script per comodità 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 usare questa costruzione di iteratore.

Quando esegui la migrazione dello script a V8, evita la funzione __iterator__ per creare iteratori personalizzati. Utilizza invece iteratori ECMAScript 6.

Considera la seguente costruzione di 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 di Rhino e come creare 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);
}
      

Evitare le clausole di cattura condizionali

Il runtime V8 non supporta le clausole di cattura condizionale catch..if, poiché non sono conformi allo standard.

Quando esegui la migrazione dello script a V8, sposta eventuali condizionali di cattura all'interno del corpo delcatch:

// 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 usare Object.prototype.toSource()

JavaScript 1.3 conteneva un metodo Object.prototype.toSource() che non faceva mai parte di nessuno standard ECMAScript. Non è supportato nel runtime V8.

Quando esegui la migrazione dello script alla versione V8, rimuovi qualsiasi utilizzo di Object.prototype.toSource() dal codice.

Altre differenze

Oltre alle incompatibilità di cui sopra 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 sezioni seguenti spiegano come aggiornare il codice dello script per evitare queste sorprese impreviste.

Modificare la formattazione di data e ora specifiche per le 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 passati vengono ignorati.

Nel runtime V8, il formato predefinito è il formato breve e i parametri trasmessi vengono gestiti in base allo standard ECMA (per maggiori dettagli, consulta la documentazione di toLocaleDateString()).

Quando esegui la migrazione dello script alla versione 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 usare Error.fileName e Error.lineNumber.

Nel untime 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 a V8, rimuovi qualsiasi dipendenza da Error.fileName e Error.lineNumber.

Un'alternativa è utilizzare Error.prototype.stack. Anche questo stack non è standard, ma è supportato sia in Rhino sia in 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)
      

Regola la gestione degli oggetti enum con stringa

Nel runtime di 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 restituito il nome enum.

Quando esegui la migrazione dello script a V8, testa e modifica le aspettative del codice riguardo 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"

Regola la gestione dei parametri non definiti

Nel runtime originale di Rhino, il passaggio di undefined a un metodo come parametro comportava il passaggio della stringa "undefined" a quel metodo.

In V8, trasferire undefined ai metodi equivale a passare null.

Quando esegui la migrazione dello script alla versione 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.

Regola gestione this globale

Il runtime di Rhino definisce un contesto speciale implicito per gli script che lo utilizzano. Il codice dello script viene eseguito in questo contesto implicito, distinto dall'effettivo this globale. Ciò significa che i riferimenti al "this globale" nel codice in realtà si valutano nel contesto speciale, che contiene solo il codice e le variabili definite nello script. I servizi Apps Script integrati e gli oggetti ECMAScript sono esclusi da questo utilizzo di this. La situazione era simile alla seguente 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.
}();

In 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 strumenti ECMAScript integrati come Math e Date.

Quando esegui la migrazione del tuo script a V8, testa e modifica le aspettative del codice relative all'utilizzo 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));
}

Regola 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.

Questo è valido solo se la libreria utilizza instanceof su un oggetto che non è stato creato nel tuo progetto. Il suo utilizzo su un oggetto creato nel tuo progetto, che sia nello stesso script o in uno diverso all'interno del progetto, dovrebbe funzionare come previsto.

Se un progetto in esecuzione su V8 utilizza il tuo script come libreria, controlla se lo script utilizza instanceof su un parametro che verrà passato da un altro progetto. Modifica l'utilizzo di instanceof e utilizza altre alternative possibili in base al tuo caso d'uso.

Un'alternativa a a instanceof b è l'utilizzo del costruttore di a nei casi in cui non sia necessario cercare l'intera catena del prototipo e controllare il costruttore. Utilizzo: a.constructor.name == "b"

Considera i progetti A e B, dove il progetto A utilizza il progetto B come biblioteca.

//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 passa la funzione in aggiunta ad altri parametri quando si chiama una funzione di libreria. La funzione passata può quindi essere utilizzata per verificare 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);
}
      

Modifica 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 trasferire una risorsa non condivisa. La libreria utilizza invece la propria risorsa.

Nel runtime V8, il passaggio di una risorsa non condivisa alla libreria funziona. La libreria utilizza la risorsa non condivisa passata.

Non trasferire risorse non condivise come parametri della funzione. Dichiara sempre le risorse non condivise nello stesso script che le utilizza.

Considera i progetti A e B, dove il progetto A utilizza il progetto B come biblioteca. 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'));
}

//Project B function setScriptProperties() { PropertiesService.getScriptProperties() .setProperty('project', 'Project-B'); } function getScriptProperties( propertiesService, key) { return propertiesService.getScriptProperties() .getProperty(key); }

// V8 runtime
// Project A
function testPassingNonSharedProperties() {
  PropertiesService.getScriptProperties()
      .setProperty('project', 'Project-A');
  B.setScriptProperties();
  // Prints: Project-A
  Logger.log(B.getScriptProperties(
      PropertiesService, 'project'));
}

// Project B function setProperties() { PropertiesService.getScriptProperties() .setProperty('project', 'Project-B'); } function getScriptProperties( propertiesService, key) { return propertiesService.getScriptProperties() .getProperty(key); }

Aggiornare 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.