Skripts zur V8-Laufzeit migrieren

Wenn Sie bereits ein Skript mit der Rhino-Laufzeit haben und die Syntax und Funktionen von V8 nutzen möchten, müssen Sie das Skript zu V8 migrieren.

Die meisten in der Rhino-Laufzeit geschriebenen Skripts können mit einer V8-Laufzeit ohne Anpassung ausgeführt werden. Oft ist das Aktivieren der V8-Laufzeit die einzige Voraussetzung für das Hinzufügen von V8-Syntax und -Funktionen zu einem Skript.

Es gibt jedoch einige Inkompatibilitäten und andere Unterschiede, die dazu führen können, dass ein Skript nach dem Aktivieren der V8-Laufzeit fehlschlägt oder sich unerwartet verhält. Wenn Sie ein Skript zur Verwendung von V8 migrieren, müssen Sie im Skriptprojekt nach diesen Problemen suchen und die gefundenen Fehler korrigieren.

V8-Migrationsverfahren

So migrieren Sie ein Skript zu V8:

  1. Aktivieren Sie die V8-Laufzeit für das Skript.
  2. Sehen Sie sich die unten aufgeführten Inkompatibilitäten sorgfältig an. Prüfen Sie Ihr Skript, um festzustellen, ob Inkompatibilitäten vorhanden sind. Falls eine oder mehrere Inkompatibilitäten vorhanden sind, passen Sie den Skriptcode an, um das Problem zu entfernen oder zu vermeiden.
  3. Sehen Sie sich die anderen unten aufgeführten Unterschiede sorgfältig an. Überprüfen Sie Ihr Skript, um festzustellen, ob sich einer der aufgeführten Unterschiede auf das Verhalten des Codes auswirkt. Passen Sie das Script an, um das Verhalten zu korrigieren.
  4. Nachdem Sie alle erkannten Inkompatibilitäten oder anderen Unterschiede behoben haben, können Sie Ihren Code aktualisieren, um die V8-Syntax und andere Funktionen nach Bedarf zu verwenden.
  5. Nachdem Sie die Codeanpassungen vorgenommen haben, testen Sie Ihr Skript gründlich, um sicherzustellen, dass es erwartungsgemäß funktioniert.
  6. Wenn Ihr Skript eine Webanwendung oder ein veröffentlichtes Add-on ist, müssen Sie eine neue Version des Skripts mit den V8-Anpassungen erstellen. Um die V8-Version für Nutzer verfügbar zu machen, müssen Sie das Skript mit dieser Version noch einmal veröffentlichen.

Inkompatibilitäten

Die ursprüngliche, auf Rhino basierende Apps Script-Laufzeit ermöglichte leider mehrere nicht standardmäßige ECMAScript-Verhaltensweisen. Da V8 standardkonform ist, wird dieses Verhalten nach der Migration nicht mehr unterstützt. Wenn diese Probleme nicht behoben werden, treten Fehler oder fehlerhaftes Skriptverhalten auf, nachdem die V8-Laufzeit aktiviert wurde.

In den folgenden Abschnitten werden diese Verhaltensweisen und Schritte beschrieben, die Sie ausführen müssen, um den Skriptcode während der Migration zu V8 zu korrigieren.

Auf for each(variable in object) verzichten

Die Anweisung for each (variable in object) wurde JavaScript 1.6 hinzugefügt und zugunsten von for...of entfernt.

Vermeiden Sie bei der Migration Ihres Skripts zu V8 die Verwendung von for each (variable in object)-Anweisungen.

Verwende stattdessen 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);
}
      

Auf Date.prototype.getYear() verzichten

In der ursprünglichen Rhino-Laufzeit gibt Date.prototype.getYear() zweistellige Jahre für Jahre von 1900 bis 1999 zurück, für andere Datumsangaben jedoch vierstellige Jahre, wie es in JavaScript 1.2 und früheren Versionen der Fall war.

In der V8-Laufzeit gibt Date.prototype.getYear() stattdessen gemäß den ECMAScript-Standards das Jahr minus 1900 zurück.

Verwenden Sie bei der Migration Ihres Skripts zu V8 immer Date.prototype.getFullYear(), das unabhängig vom Datum ein vierstelliges Jahr zurückgibt.

Verwenden Sie keine reservierten Keywords als Namen.

ECMAScript verbietet die Verwendung bestimmter reservierter Schlüsselwörter in Funktions- und Variablennamen. In der Rhino-Laufzeit waren viele dieser Wörter zulässig. Wenn sie in Ihrem Code verwendet werden, müssen Sie die Funktionen oder Variablen umbenennen.

Bei der Migration Ihres Skripts zu V8 sollten Sie Variablen oder Funktionen nicht mit einem der reservierten Keywords benennen. Benennen Sie jede Variable oder Funktion um, damit sie nicht den Schlüsselwortnamen verwendet. Keywords werden häufig als Namen verwendet: class, import und export.

Vermeiden Sie die Neuzuweisung von const-Variablen

In der ursprünglichen Rhino-Laufzeit können Sie eine Variable mit const deklarieren. Das bedeutet, dass sich der Wert des Symbols nie ändert und zukünftige Zuweisungen des Symbols ignoriert werden.

In der neuen V8-Laufzeit ist das Schlüsselwort const standardkonform. Die Zuweisung einer als const deklarierten Variablen führt zu einem TypeError: Assignment to constant variable-Laufzeitfehler.

Versuchen Sie bei der Migration Ihres Skripts zu V8 nicht, den Wert einer const-Variablen neu zuzuweisen:

// 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
      

XML-Literale und das XML-Objekt vermeiden

Durch diese nicht standardmäßige Erweiterung für ECMAScript können Apps Script-Projekte direkt die XML-Syntax verwenden.

Vermeiden Sie bei der Migration Ihres Skripts zu V8 die Verwendung von direkten XML-Literalen oder dem XML-Objekt.

Verwenden Sie zum Parsen von XML stattdessen XmlService:

// 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
      

Keine benutzerdefinierten Iterationsfunktionen mit __iterator__ erstellen

In JavaScript 1.7 wurde eine Funktion hinzugefügt, mit der beliebigen Klauseln ein benutzerdefinierter Iteration hinzugefügt werden kann. Dazu muss im Prototyp der Klasse eine __iterator__-Funktion deklariert werden. Diese Funktion wurde auch in die Rhino-Laufzeit von Apps Script eingefügt, um den Entwicklern die Arbeit zu erleichtern. Diese Funktion war jedoch nie Teil des ECMA-262-Standards und wurde in ECMAScript-kompatiblen JavaScript-Engines entfernt. In Scripts mit V8 kann diese Iteration nicht verwendet werden.

Vermeiden Sie bei der Migration Ihres Skripts zu V8 die __iterator__-Funktion, um benutzerdefinierte Iterationen zu erstellen. Verwenden Sie stattdessen ECMAScript 6-Iterationen.

Betrachten Sie die folgende Array-Konstruktion:

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

Die folgenden Codebeispiele zeigen, wie ein Iteration in der Rhino-Laufzeit erstellt werden kann und wie ein Ersatz-iterator in der V8-Laufzeit erstellt wird:

// 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);
}
      

Bedingte Catchall-Klauseln vermeiden

Die V8-Laufzeit unterstützt keine bedingten catch..if-Catchall-Klauseln, da sie nicht standardkonform sind.

Verschieben Sie bei der Migration Ihres Skripts zu V8 alle Catch-Bedingungen in den Catch-Text:

// 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
  }
}

Object.prototype.toSource() vermeiden

JavaScript 1.3 enthielt die Methode Object.prototype.toSource(), die nie Teil eines ECMAScript-Standards war. Es wird in der V8-Laufzeit nicht unterstützt.

Entfernen Sie bei der Migration Ihres Skripts zu V8 jegliche Verwendung von Object.prototype.toSource() aus Ihrem Code.

Weitere Unterschiede

Zusätzlich zu den oben genannten Inkompatibilitäten, die Skriptfehler verursachen können, gibt es noch einige andere Unterschiede, die, wenn sie nicht behoben werden, zu unerwartetem Verhalten des V8-Laufzeitskripts führen können.

In den folgenden Abschnitten wird erläutert, wie Sie Ihren Skriptcode aktualisieren, um diese unerwarteten Überraschungen zu vermeiden.

Länderspezifische Datums- und Uhrzeitformatierung anpassen

Die Date-Methoden toLocaleString(), toLocaleDateString() und toLocaleTimeString() verhalten sich in der V8-Laufzeit anders als in Rhino.

In Rhino ist das Standardformat das lange Format. Alle übergebenen Parameter werden ignoriert.

In der V8-Laufzeit ist das Standardformat das Kurzformat und die übergebenen Parameter werden gemäß dem ECMA-Standard behandelt. Weitere Informationen finden Sie in der Dokumentation zu toLocaleDateString().

Testen und passen Sie bei der Migration Ihres Skripts zu V8 die Erwartungen Ihres Codes in Bezug auf die Ausgabe sprachspezifischer Datums- und Uhrzeitmethoden an:

// 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' }));
      

Error.fileName und Error.lineNumber vermeiden

Im V8-Untime-Modus unterstützt das Standard-JavaScript-Objekt Error fileName oder lineNumber nicht als Konstruktorparameter oder Objekteigenschaften.

Entfernen Sie bei der Migration Ihres Skripts zu V8 jegliche Abhängigkeit von Error.fileName und Error.lineNumber.

Alternativ können Sie Error.prototype.stack verwenden. Dieser Stack ist ebenfalls nicht Standard, wird aber sowohl in Rhino als auch in V8 unterstützt. Das Format des von den beiden Plattformen generierten Stacktrace unterscheidet sich geringfügig:

// 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)
      

Verarbeitung von Enum-Objekten als String anpassen

Wenn in der ursprünglichen Rhino-Laufzeit die JavaScript-Methode JSON.stringify() für ein Enum-Objekt verwendet wird, wird nur {} zurückgegeben.

In V8 wird bei Verwendung derselben Methode für ein Enum-Objekt der Enum-Name gelöscht.

Testen Sie bei der Migration Ihres Skripts zu V8 die Codeerwartungen an die Ausgabe von JSON.stringify() auf Enum-Objekte und passen Sie sie an:

// 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"

Verarbeitung nicht definierter Parameter anpassen

In der ursprünglichen Rhino-Laufzeit wurde nach der Übergabe von undefined als Parameter der String "undefined" an diese Methode übergeben.

In V8 entspricht die Übergabe von undefined an Methoden der Übergabe von null.

Testen Sie bei der Migration Ihres Skripts zu V8 die Erwartungen Ihres Codes in Bezug auf undefined-Parameter und passen Sie sie an:

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

Verarbeitung der globalen this anpassen

Die Rhino-Laufzeit definiert einen impliziten speziellen Kontext für Skripts, die ihn verwenden. Der Skriptcode wird in diesem impliziten Kontext ausgeführt, unabhängig vom tatsächlichen globalen this. Dies bedeutet, dass Verweise auf das „globale this“ im Code tatsächlich in den speziellen Kontext ausgewertet werden, der nur den Code und die Variablen enthält, die im Skript definiert sind. Die integrierten Apps Script-Dienste und ECMAScript-Objekte sind von dieser Verwendung von this ausgeschlossen. Diese Situation ähnelte der folgenden JavaScript-Struktur:

// 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 wird der implizite spezielle Kontext entfernt. Globale Variablen und Funktionen, die im Skript definiert sind, werden im globalen Kontext neben den integrierten Apps Script-Diensten und den integrierten ECMAScript-Diensten wie Math und Date platziert.

Testen Sie bei der Migration Ihres Skripts zu V8 die Erwartungen Ihres Codes in Bezug auf die Verwendung von this in einem globalen Kontext und passen Sie sie an. In den meisten Fällen sind die Unterschiede nur sichtbar, wenn Ihr Code die Schlüssel oder Attributnamen des globalen this-Objekts prüft:

// 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));
}

Verarbeitung von instanceof in Bibliotheken anpassen

Die Verwendung von instanceof in einer Bibliothek für ein Objekt, das als Parameter in einer Funktion von einem anderen Projekt übergeben wird, kann falsch negative Ergebnisse liefern. In der V8-Laufzeit werden ein Projekt und seine Bibliotheken in verschiedenen Ausführungskontexten ausgeführt und haben daher unterschiedliche globale und Prototypketten.

Dies ist nur der Fall, wenn Ihre Bibliothek instanceof für ein Objekt verwendet, das nicht in Ihrem Projekt erstellt wurde. Es sollte wie erwartet funktionieren, wenn Sie es für ein Objekt verwenden, das in Ihrem Projekt in demselben oder einem anderen Skript innerhalb Ihres Projekts erstellt wird.

Wenn ein Projekt, das auf V8 ausgeführt wird, Ihr Skript als Bibliothek verwendet, prüfen Sie, ob Ihr Skript instanceof für einen Parameter verwendet, der von einem anderen Projekt übergeben wird. Passen Sie die Nutzung von instanceof an und verwenden Sie je nach Anwendungsfall andere durchführbare Alternativen.

Eine Alternative für a instanceof b kann die Verwendung des Konstruktors von a sein, wenn Sie nicht die gesamte Prototypkette durchsuchen und nur den Konstruktor prüfen müssen. Nutzung: a.constructor.name == "b"

Sehen wir uns Projekt A und Projekt B an, bei denen Projekt A Projekt B als Bibliothek verwendet.

//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
}

Eine weitere Alternative kann die Einführung einer Funktion sein, die instanceof im Hauptprojekt prüft und die Funktion beim Aufrufen einer Bibliotheksfunktion neben anderen Parametern übergibt. Mit der übergebenen Funktion kann dann instanceof in der Bibliothek geprüft werden.

//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);
}
      

Übergabe nicht gemeinsam genutzter Ressourcen an Bibliotheken anpassen

Die Übergabe einer nicht freigegebenen Ressource vom Hauptskript an eine Bibliothek funktioniert in der V8-Laufzeit anders.

In der Rhino-Laufzeit funktioniert das Übergeben einer nicht freigegebenen Ressource nicht. Die Bibliothek verwendet stattdessen ihre eigene Ressource.

In der V8-Laufzeit funktioniert das Übergeben einer nicht freigegebenen Ressource an die Bibliothek. Die Bibliothek verwendet die übergebene nicht freigegebene Ressource.

Übergeben Sie keine nicht freigegebenen Ressourcen als Funktionsparameter. Deklarieren Sie nicht freigegebene Ressourcen immer in dem Skript, in dem sie verwendet werden.

Sehen wir uns Projekt A und Projekt B an, bei denen Projekt A Projekt B als Bibliothek verwendet. In diesem Beispiel ist PropertiesService eine nicht freigegebene Ressource.

// 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); }

Zugriff auf eigenständige Skripts aktualisieren

Bei eigenständigen Skripts, die in der V8-Laufzeit ausgeführt werden, müssen Sie den Nutzern mindestens Lesezugriff auf das Skript gewähren, damit die Trigger des Skripts ordnungsgemäß funktionieren.