Migracja skryptów do środowiska wykonawczego V8

Jeśli masz istniejący skrypt używający środowiska wykonawczego Rhino i chcesz użyć ze składni i funkcji V8, musisz przenieść skrypt do wersji 8.

Większość skryptów napisanych w środowisku wykonawczym Rhino może działać w środowisku V8 bez regulacji. Często jedynym wymogiem wstępnym po dodaniu składni V8 cech skryptu włączenie środowiska wykonawczego V8.

Istnieje jednak niewielki zestaw niezgodności i inne różnice, które mogą spowodować, że skrypt jeśli po włączeniu środowiska wykonawczego V8 występują błędy lub nie działa ono w nieoczekiwany sposób. Podczas przenoszenia skryptu do używania V8 musisz przeszukać projekt skryptu pod kątem tych problemów i je naprawić.

Procedura migracji V8

Aby przenieść skrypt do wersji 8, wykonaj te czynności:

  1. Włącz środowisko wykonawcze V8 dla skryptu.
  2. Dokładnie zapoznaj się z niezgodnościami. wymienionych poniżej. Sprawdź skrypt, aby ustalić, czy występują w nim jakieś niezgodności. Jeśli tak, zmień kod skryptu, aby usunąć problem lub go uniknąć.
  3. Uważnie zapoznaj się z innymi różnicami wymienionymi poniżej. Sprawdź skrypt, aby określić, czy któraś z wymienionych różnic ma wpływ działania kodu. Zmień skrypt, aby poprawić zachowanie.
  4. Po wyeliminowaniu wszelkich wykrytych niezgodności różnic, możesz zaktualizować kod, aby użyć Składnia V8 i inne funkcje zgodnie z potrzebami.
  5. Po wprowadzeniu poprawek w kodzie dokładnie przetestuj skrypt, aby zapewnić aby upewnić się, że działa zgodnie z oczekiwaniami.
  6. Jeśli skrypt jest aplikacją internetową lub opublikowanym dodatkiem, musisz utwórz nową wersję z korektami V8. Aby udostępnić wersję V8 , musisz ponownie opublikować skrypt z tą wersją.

Niezgodności

Oryginalne środowisko wykonawcze Apps Script oparte na Rhino zezwalało na niestandardowe zachowania ECMAScript. Ponieważ V8 jest zgodne ze standardami, nie są obsługiwane po migracji. Brak rozwiązania tych problemów włączenie środowiska wykonawczego V8 powoduje błędy lub uszkodzenie skryptu.

W następnych sekcjach opisujemy te zachowania i działania, które należy wykonać, aby poprawić kod skryptu podczas migracji do V8.

Unikaj: for each(variable in object)

for each (variable in object) została dodana do JavaScript w wersji 1.6 i usunięta na rzecz for...of.

Podczas przenoszenia skryptu do wersji 8 unikaj używania for each (variable in object) wyciągów.

Zamiast niego użyj zasady 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);
}
      

Unikaj: Date.prototype.getYear()

W oryginalnym środowisku Rhino Date.prototype.getYear() zwraca lata 2-cyfrowe dla lat 1900-1999, ale czterocyfrowych lat w przypadku pozostałych co było stosowane w JavaScript w wersji 1.2 i starszych.

W środowisku wykonawczym V8 Date.prototype.getYear() zwraca rok minus 1900 zgodnie z wymaganym przez standardów ECMAScript.

Przy migracji skryptu do wersji 8 zawsze używaj Date.prototype.getFullYear(), , który zwraca czterocyfrowy rok niezależnie od daty.

Unikaj używania zastrzeżonych słów kluczowych jako nazw

ECMAScript zabrania stosowania określonych zarezerwowanych słów kluczowych, w nazwach funkcji i zmiennych. Środowisko wykonawcze Rhino zezwalało na wiele z tych słów, więc jeśli Twój kod ich używa, musisz zmienić nazwy funkcji lub zmiennych.

Podczas przenoszenia skryptu do wersji 8 unikaj nazywania zmiennych i funkcji za pomocą jednej z zarezerwowanych słów kluczowych. Zmień nazwy zmiennych lub funkcji, aby nie używać nazwy słowa kluczowego. Częste zastosowania słów kluczowych jako nazw to class, import i export.

Unikaj ponownego przypisywania const zmiennych

W pierwotnym środowisku Rhino możesz zadeklarować zmienną za pomocą atrybutu const, który oznacza, że wartość symbolu nigdy się nie zmienia i przyszłe przypisania do są ignorowane.

W nowym środowisku wykonawczym V8 kluczowe słowo const jest zgodne ze standardem, a przypisanie do zmiennej zadeklarowanej jako const powoduje błąd wykonawczy TypeError: Assignment to constant variable.

Podczas migracji skryptu do wersji 8 nie próbuj ponownie przypisywać wartości zmienną 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
      

Unikaj literałów XML i obiektu XML

Ta niestandardowa rozszerzona wersja ECMAScript umożliwia projektom Apps Script bezpośrednie korzystanie z składni XML.

Podczas migracji skryptu do wersji 8 unikaj używania bezpośrednich literałów XML oraz kodu XML .

Zamiast tego użyj XmlService do przeanalizuj 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
      

Nie twórz niestandardowych funkcji iteratora za pomocą __iterator__

W JavaScripcie 1.7 dodano funkcję umożliwiającą dodanie niestandardowego iteratora do każdej klamy s przez zadeklarowanie funkcji __iterator__ w prototypie tej klasy; to było dodaliśmy też do środowiska wykonawczego Apps Script Rhino, co jest udogodnieniem dla programistów. Ta funkcja nigdy jednak nie była częścią standardu ECMA-262 i została usunięta z silników JavaScript zgodnych ze standardem ECMAScript. Skrypty korzystające z V8 nie można użyć tej konstrukcji iteratora.

Podczas migracji skryptu do wersji 8 unikaj funkcji __iterator__ przy kompilowaniu niestandardowych iteratorów. Zamiast tego użyj przeglądaczy ECMAScript 6.

Rozważ taką konstrukcję macierzową:

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

Poniższe przykłady kodu pokazują, jak można skonstruować iterator w funkcji środowiska wykonawczego Rhino oraz instrukcje tworzenia iteratora zastępczego w środowisku wykonawczym 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);
}
      

Unikaj warunkowych klauzul przechwytywania

Środowisko wykonawcze V8 nie obsługuje klauzul catch..if, ponieważ nie są one zgodne ze standardem.

Podczas migracji skryptu do wersji 8 przenieś wszystkie warunki przechwytywania do wewnątrz tagu [cat 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
  }
}

Unikaj stosowania atrybutu Object.prototype.toSource()

JavaScript 1.3 zawierał Object.prototype.toSource() która nigdy nie była częścią żadnego standardu ECMAScript. Nie jest obsługiwane w tych krajach: w środowisku wykonawczym V8.

Podczas migracji skryptu do wersji 8 usuń wszelkie użycie Object.prototype.toSource() z kodu.

Inne różnice

Oprócz powyższych niezgodności, które mogą powodować błędy skryptów, występuje też to kilka innych różnic, które – jeśli nie zostaną skorygowane, mogą spowodować nieoczekiwany V8 działania skryptu środowiska wykonawczego.

W następnych sekcjach wyjaśniamy, jak zaktualizować kod skryptu, aby uniknąć takich niespodzianek.

Dostosowywanie formatowania daty i godziny zależnie od języka

Date metody toLocaleString(), toLocaleDateString(), i toLocaleTimeString() działają w środowisku wykonawczym V8 inaczej niż w Rhino.

W Rhino domyślnym formatem jest długi format, a wszystkie parametry przekazywane w są ignorowane.

W środowisku wykonawczym V8 domyślnym formatem jest krótki format i parametry przekazywane są obsługiwane zgodnie ze standardem ECMA (zobacz Dokumentacja toLocaleDateString() ).

Podczas przenoszenia skryptu do wersji 8 przetestuj i dostosuj swoje oczekiwania wobec kodu o wynikach metod daty i godziny specyficznych dla języka:

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

Unikaj używania atrybutów Error.fileName i Error.lineNumber

W czasie działania V8 standardowy kod JavaScript Error obiekt nie obsługuje fileName ani lineNumber jako parametrów konstruktora lub właściwości obiektu.

Podczas migracji skryptu do wersji 8 usuń wszelkie zależności od Error.fileName i Error.lineNumber.

Alternatywnym rozwiązaniem jest użycie Error.prototype.stack Ten stos jest również niestandardowy, ale obsługiwany zarówno przez Rhino, jak i V8. format zrzutu stosu wygenerowanego przez te 2 platformy jest nieco inny:

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

Dostosuj obsługę obiektów wyliczeniowych w postaci ciągu znaków

W pierwotnym środowisku wykonawczym Rhino za pomocą JavaScriptu JSON.stringify() w obiekcie wyliczenia zwraca tylko {}.

W wersji 8 użycie tej samej metody w obiekcie wyliczenia powoduje zmianę nazwy wyliczenia.

Podczas migracji skryptu do V8 przetestuj i dostosuj oczekiwania kodu dotyczące danych wyjściowych funkcji JSON.stringify() w przypadku obiektów 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"

Dostosowywanie obsługi niezdefiniowanych parametrów

W pierwotnym środowisku wykonawczym Rhino przekazywanie parametru undefined do metody jako parametru. zaowocowało przekazaniem ciągu "undefined" do tej metody.

W wersji 8 przekazanie undefined do metod jest równoważne z przekazywaniem wartości null.

Podczas migracji skryptu do wersji 8 Sprawdź i dostosuj oczekiwania kodu związane z parametrami 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.

Dostosuj obsługę globalnego zasobu this

Środowisko wykonawcze Rhino definiuje domyślny kontekst specjalny dla skryptów, które go używają. Kod skryptu działa w tym niejawnym kontekście, inny niż rzeczywisty globalny this Oznacza to, że odwołania do atrybutu „this” w kodzie są analizowane w specjalnym kontekście, który zawiera tylko kod i zmienne w skrypcie. wbudowane usługi Apps Script i obiekty ECMAScript; nie można używać w tych usługach: this. Ta sytuacja była podobna do tej struktury 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.
}();

W V8 domyślny kontekst specjalny jest usuwany. Zmienne i funkcje globalne zdefiniowane w skrypcie są umieszczane w kontekście globalnym, obok wbudowanych Usługi Apps Script i wbudowane skrypty ECMAScript, np. Math i Date.

Podczas przenoszenia skryptu do wersji 8 przetestuj i dostosuj swoje oczekiwania wobec kodu dotyczące używania this w kontekście globalnym. W większości przypadków różnice są widoczne tylko wtedy, gdy kod sprawdza klucze lub nazwy właściwości globalny obiekt this:

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

Dostosuj obsługę pliku instanceof w bibliotekach

Użycie funkcji instanceof w bibliotece w obiekcie przekazywanym jako parametr w funkcja z innego projektu może dawać wyniki fałszywie negatywne. W środowisku wykonawczym V8 projekt i jego biblioteki są uruchamiane w różnych kontekstach wykonania, a więc mają różne zmienne globalne i łańcuchy prototypów.

Pamiętaj, że tak się dzieje tylko wtedy, gdy Twoja biblioteka używa instanceof do obiektu, który nie został utworzony w Twoim projekcie. Użycie go na obiekcie utworzonym w Twoim projekcie (w tym samym lub innym skrypcie) powinno działać zgodnie z oczekiwaniami.

Jeśli projekt uruchomiony w wersji 8 używa Twojego skryptu jako biblioteki, sprawdź, czy skrypt używa parametru instanceof, który zostanie przekazany z innego projektu. Dostosuj korzystanie z usługi instanceof i korzystanie z innych realnych alternatyw zgodnie z Twoimi potrzebami

Alternatywą dla a instanceof b jest użycie konstruktora a w których przypadku nie trzeba przeszukiwać całego łańcucha prototypów za pomocą konstruktora. Wykorzystanie: a.constructor.name == "b"

Weźmy pod uwagę Projekt A i Projekt B, w ramach którego Projekt A wykorzystuje Projekt B jako bibliotekę.

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

Inną możliwością jest wprowadzenie funkcji, która sprawdza instanceof w głównym projekcie i przekazują ją razem z innymi parametrami przy wywoływaniu funkcji biblioteki. Przekazana funkcja można następnie użyć do sprawdzenia elementu instanceof w bibliotece.

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

Dostosuj przekazywanie nieudostępnionych zasobów do bibliotek

Przekazywanie niedzielonemu zasoby ze skryptu głównego do biblioteki działa inaczej w środowisku wykonawczym V8.

W środowisku wykonawczym Rhino przekazanie zasobów nieudostępnionych nie zadziała. Biblioteka używa własnych zasobów.

W środowisku wykonawczym V8 przesyłanie nieudostępnionych zasobów do biblioteki działa. Biblioteka korzysta z przekazanego zasobu niewspółdzielonego.

Nie przekazuj nieudostępnionych zasobów jako parametrów funkcji. Zasoby nieudostępnione zawsze należy deklarować w tym samym skrypcie, który ich używa.

Rozważmy projekt A i projekt B, w którym projekt A używa projektu B jako biblioteki. W tym przykładzie PropertiesService jest niewspólnym zasobem.

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

Zaktualizuj dostęp do samodzielnych skryptów

W przypadku samodzielnych skryptów działających w środowisku wykonawczym V8 musisz zapewnić użytkownikom co najmniej wyświetlanie dostępu do skryptu, aby zapewnić prawidłowe działanie wyzwalaczy skryptu.