Skripts zur V8-Laufzeit migrieren

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

Die meisten Scripts, die mit der Rhino-Laufzeit geschrieben wurden, können ohne Anpassung mit einer V8-Laufzeit ausgeführt werden. Oft ist die einzige Voraussetzung für das Hinzufügen von V8-Syntax und ‑Funktionen zu einem Script die Aktivierung der V8-Laufzeit.

Es gibt jedoch einige Inkompatibilitäten und andere Unterschiede, die dazu führen können, dass ein Script nach der Aktivierung der V8-Laufzeit fehlschlägt oder sich unerwartet verhält. Wenn Sie ein Script auf V8 umstellen, müssen Sie das Scriptprojekt auf diese Probleme prüfen und alle gefundenen Fehler korrigieren.

Migrationsverfahren für V8

So migrieren Sie ein Script zu V8:

  1. Aktivieren Sie die V8-Laufzeit für das Script.
  2. Lesen Sie sich die unten aufgeführten Inkompatibilitäten sorgfältig durch. Prüfen Sie Ihr Script, um festzustellen, ob eine oder mehrere der Inkompatibilitäten vorliegen. Falls ja, passen Sie den Scriptcode an, um das Problem zu beheben oder zu vermeiden.
  3. Lesen Sie sich die anderen Unterschiede unten sorgfältig durch. Prüfen Sie Ihr Script, um festzustellen, ob sich eine der aufgeführten Unterschiede auf das Verhalten Ihres Codes auswirkt. Passen Sie das Script an, um das Problem zu beheben.
  4. Sobald Sie alle gefundenen Inkompatibilitäten oder anderen Unterschiede korrigiert haben, können Sie Ihren Code so aktualisieren, dass er die V8-Syntax und andere Funktionen verwendet.
  5. Testen Sie Ihr Script gründlich, nachdem Sie die Codeanpassungen vorgenommen haben, um sicherzustellen, dass es wie erwartet funktioniert.
  6. Wenn es sich bei Ihrem Script um eine Webanwendung oder ein veröffentlichtes Add-on handelt, müssen Sie eine neue Version des Scripts mit den V8-Anpassungen erstellen. Damit die V8-Version für Nutzer verfügbar ist, müssen Sie das Script noch einmal mit dieser Version veröffentlichen.

Inkompatibilitäten

Die ursprüngliche Rhino-basierte Apps Script-Laufzeit erlaubte leider mehrere nicht standardmäßige ECMAScript-Verhaltensweisen. Da V8 standardskonform ist, werden diese Verhaltensweisen nach der Migration nicht mehr unterstützt. Wenn diese Probleme nicht behoben werden, kommt es nach der Aktivierung der V8-Laufzeit zu Fehlern oder einem fehlerhaften Script-Verhalten.

In den folgenden Abschnitten werden die einzelnen Verhaltensweisen und die Schritte beschrieben, die Sie ausführen müssen, um Ihren Scriptcode bei der Migration zu V8 zu korrigieren.

Auf for each(variable in object) verzichten

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

Verwenden Sie bei der Migration Ihres Scripts zu V8 keine for each (variable in object)-Anweisungen.

Verwenden Sie 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-Laufzeitumgebung gibt Date.prototype.getYear() zweistellige Jahre für die Jahre 1900–1999 zurück, aber vierstellige Jahre für andere Datumsangaben. Dies entspricht dem Verhalten in JavaScript 1.2 und niedriger.

In der V8-Laufzeit gibt Date.prototype.getYear() stattdessen das Jahr abzüglich 1900 zurück, wie es die ECMAScript-Standards erfordern.

Verwenden Sie bei der Migration Ihres Scripts zu V8 immer Date.prototype.getFullYear(). Damit wird unabhängig vom Datum ein vierstelliges Jahr zurückgegeben.

Vermeiden Sie die Verwendung reservierter Keywords als Namen.

ECMAScript verbietet die Verwendung bestimmter reservierter Keywords in Funktions- und Variablennamen. Die Rhino-Laufzeit erlaubte viele dieser Wörter. Wenn Sie sie in Ihrem Code verwenden, müssen Sie Ihre Funktionen oder Variablen umbenennen.

Verwenden Sie beim Migrieren Ihres Scripts zu V8 keine reservierten Keywords für Variablen oder Funktionen. Benennen Sie alle Variablen oder Funktionen um, um den Namen des Keywords zu vermeiden. Gängige Keywords für Namen sind class, import und export.

const-Variablen nicht neu zuweisen

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 an das Symbol ignoriert werden.

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

Versuchen Sie bei der Migration Ihres Scripts 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
      

Vermeiden Sie XML-Literale und das XML-Objekt

Mit dieser nicht standardmäßigen Erweiterung von ECMAScript können Apps Script-Projekte die XML-Syntax direkt verwenden.

Verwenden Sie beim Migrieren Ihres Scripts zu V8 keine direkten XML-Literale oder das XML-Objekt.

Verwenden Sie stattdessen den XmlService, um XML zu parsen:

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

Benutzerdefinierte Iteratorfunktionen nicht mit __iterator__ erstellen

In JavaScript 1.7 wurde eine Funktion hinzugefügt, mit der jeder Klasse ein benutzerdefinierter Iterator hinzugefügt werden kann. Dazu wird im Prototyp der Klasse eine __iterator__-Funktion deklariert. Diese Funktion wurde auch der Rhino-Laufzeit von Apps Script hinzugefügt, um Entwicklern die Arbeit zu erleichtern. Diese Funktion war jedoch nie Teil des ECMA-262-Standards und wurde aus ECMAScript-kompatiblen JavaScript-Engines entfernt. Bei Scripts, die V8 verwenden, kann diese Iteratorkonstruktion nicht verwendet werden.

Verwenden Sie beim Migrieren Ihres Scripts zu V8 keine __iterator__-Funktion, um benutzerdefinierte Iteratoren zu erstellen. Verwenden Sie stattdessen ECMAScript 6-Iterator.

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.
      

In den folgenden Codebeispielen wird gezeigt, wie ein Iterator in der Rhino-Laufzeit und wie ein Ersatziterator in der V8-Laufzeit erstellt werden kann:

// 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 Catch-Klauseln vermeiden

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

Verschieben Sie bei der Migration Ihres Scripts zu V8 alle Catch-Bedingungen in den Catch-Body:

// 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. Sie wird in der V8-Laufzeit nicht unterstützt.

Entfernen Sie bei der Migration Ihres Scripts zu V8 alle Verwendungen von Object.prototype.toSource() aus Ihrem Code.

Weitere Unterschiede

Zusätzlich zu den oben genannten Inkompatibilitäten, die zu Scriptfehlern führen können, gibt es einige weitere Unterschiede, die bei Nichtbehebung zu unerwartetem Verhalten von V8-Laufzeitscripts führen können.

In den folgenden Abschnitten wird beschrieben, wie Sie Ihren Scriptcode aktualisieren, um solche unerwarteten Überraschungen zu vermeiden.

Landesspezifische 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 Langformat das Standardformat und alle übergebenen Parameter werden ignoriert.

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

Wenn Sie Ihr Script zu V8 migrieren, testen und passen Sie die Erwartungen Ihres Codes an die Ausgabe von länderspezifischen 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' }));
      

Vermeiden Sie die Verwendung von Error.fileName und Error.lineNumber

In der V8-Untime-Umgebung unterstützt das standardmäßige JavaScript-Objekt Error weder fileName noch lineNumber als Konstruktorparameter oder Objekteigenschaften.

Entfernen Sie bei der Migration Ihres Scripts zu V8 alle Abhängigkeiten von Error.fileName und Error.lineNumber.

Alternativ können Sie auch die Error.prototype.stack verwenden. Dieser Stack ist ebenfalls nicht standardmäßig, wird aber sowohl in Rhino als auch in V8 unterstützt. Das Format des Stack-Traces, der von den beiden Plattformen generiert wird, 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)
      

Umgang mit stringifizierten Enum-Objekten anpassen

In der ursprünglichen Rhino-Laufzeit wird bei Verwendung der JavaScript-Methode JSON.stringify() für ein Enum-Objekt nur {} zurückgegeben.

In V8 wird mit derselben Methode für ein Enum-Objekt der Name des Enumerations zurückgegeben.

Wenn Sie Ihr Script zu V8 migrieren, testen und passen Sie die Erwartungen Ihres Codes an die Ausgabe von JSON.stringify() auf Enum-Objekte 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"

Anpassung der Handhabung nicht definierter Parameter

In der ursprünglichen Rhino-Laufzeit wurde undefined als Parameter an eine Methode übergeben, was dazu führte, dass der String "undefined" an diese Methode übergeben wurde.

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

Wenn Sie Ihr Script zu V8 migrieren, testen und passen Sie die Erwartungen Ihres Codes bezüglich undefined-Parametern 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.

Umgang mit globalen this anpassen

Die Rhino-Laufzeit definiert einen impliziten speziellen Kontext für Scripts, die sie verwenden. Scriptcode wird in diesem impliziten Kontext ausgeführt, der sich vom tatsächlichen globalen this unterscheidet. Das bedeutet, dass Verweise auf „global this“ im Code tatsächlich auf den speziellen Kontext ausgewertet werden, der nur den im Script definierten Code und die Variablen enthält. Die integrierten Apps Script-Dienste und ECMAScript-Objekte sind von dieser Verwendung von this ausgenommen. Diese Situation ähnelte dieser 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. Im Script definierte globale Variablen und Funktionen werden neben den integrierten Apps Script-Diensten und ECMAScript-Einbauten wie Math und Date in den globalen Kontext eingefügt.

Wenn Sie Ihr Script zu V8 migrieren, testen und passen Sie die Erwartungen Ihres Codes an die Verwendung von this in einem globalen Kontext an. In den meisten Fällen sind die Unterschiede nur erkennbar, wenn in Ihrem Code die Schlüssel oder Property-Namen des globalen this-Objekts geprüft werden:

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

Umgang mit instanceof in Bibliotheken anpassen

Wenn Sie instanceof in einer Bibliothek auf einem Objekt verwenden, das als Parameter in einer Funktion aus einem anderen Projekt übergeben wird, kann es zu Falsch-Negativ-Ergebnissen kommen. In der V8-Laufzeit werden ein Projekt und seine Bibliotheken in verschiedenen Ausführungskontexten ausgeführt und haben daher unterschiedliche globale Variablen und Prototyp-Ketten.

Dies ist nur der Fall, wenn in Ihrer Bibliothek instanceof für ein Objekt verwendet wird, das nicht in Ihrem Projekt erstellt wurde. Die Verwendung auf einem Objekt, das in Ihrem Projekt erstellt wird, sei es im selben oder in einem anderen Script in Ihrem Projekt, sollte wie erwartet funktionieren.

Wenn Ihr Script in einem Projekt verwendet wird, das unter V8 ausgeführt wird, prüfen Sie, ob in Ihrem Script instanceof für einen Parameter verwendet wird, der von einem anderen Projekt übergeben wird. Passen Sie die Verwendung von instanceof an und verwenden Sie je nach Anwendungsfall andere praktikable Alternativen.

Eine Alternative für a instanceof b ist der Konstruktor von a, wenn Sie nicht die gesamte Prototypkette durchsuchen, sondern nur den Konstruktor prüfen möchten. Nutzung: a.constructor.name == "b"

Angenommen, Projekt A verwendet Projekt B als Bibliothek.

//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 besteht darin, eine Funktion einzuführen, die instanceof im Hauptprojekt prüft und die Funktion zusätzlich zu anderen Parametern beim Aufrufen einer Bibliotheksfunktion ü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);
}
      

Weitergabe nicht freigegebener Ressourcen an Bibliotheken anpassen

Das Übergeben einer nicht freigegebenen Ressource aus dem Hauptscript 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 eine 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 Script, in dem sie verwendet werden.

Angenommen, Projekt A verwendet Projekt B als Bibliothek. 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 Scripts aktualisieren

Für eigenständige Scripts, die in der V8-Laufzeit ausgeführt werden, müssen Sie Nutzern mindestens Lesezugriff auf das Script gewähren, damit die Trigger des Scripts ordnungsgemäß funktionieren.