Migracja skryptów do środowiska wykonawczego V8

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

Większość skryptów napisanych za pomocą środowiska wykonawczego Rhino może działać w środowisku wykonawczym V8 bez konieczności wprowadzania zmian. Często jedynym warunkiem wstępnym do dodania składni i funkcji V8 do skryptu jest włączenie środowiska wykonawczego V8.

Istnieje jednak pewien zestaw niezgodnościinnych różnic, które mogą spowodować, że skrypt nie będzie działać lub będzie działać nieoczekiwanie po włączeniu środowiska wykonawczego V8. Podczas migracji skryptu do V8 musisz sprawdzić projekt skryptu pod kątem tych problemów i je naprawić.

Procedura migracji do V8

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

  1. Włącz środowisko wykonawcze V8 dla skryptu.
  2. Uważnie zapoznaj się z poniżej wymienionymi niezgodnościami. 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 wpływa na działanie kodu. Zmodyfikuj skrypt, aby poprawić zachowanie.
  4. Po poprawieniu wszystkich wykrytych niezgodności lub innych różnic możesz zacząć aktualizować kod, aby używać składni V8 i innych funkcji.
  5. Po zakończeniu modyfikacji kodu dokładnie przetestuj skrypt, aby mieć pewność, że działa zgodnie z oczekiwaniami.
  6. Jeśli skrypt jest aplikacją internetową lub opublikowanym dodatkiem, musisz utworzyć nową wersję skryptu z dostosowaniem do V8. Aby udostępnić użytkownikom wersję V8, musisz ponownie opublikować skrypt z tą wersją.

Niezgodności

Pierwotne środowisko wykonawcze Apps Script oparte na Rhino zezwalało niestety na kilka niestandardowych zachowań ECMAScript. Ponieważ V8 jest zgodny ze standardami, te zachowania nie są obsługiwane po migracji. Nieusunięcie tych problemów spowoduje błędy lub nieprawidłowe działanie skryptu po włączeniu środowiska wykonawczego V8.

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)

Instrukcja for each (variable in object) została dodana do JavaScript 1.6, a następnie usunięta na rzecz instrukcji for...of.

Podczas migracji skryptu do V8 unikaj instrukcji for each (variable in object).

Zamiast tego użyj 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 pierwotnej wersji Rhino runtime funkcja Date.prototype.getYear() zwraca dwucyfrowe lata w przypadku lat 1900–1999, ale czterocyfrowe w przypadku innych dat. Takie zachowanie było charakterystyczne dla JavaScript 1.2 i starszych wersji.

W środowisku uruchomieniowym V8 element Date.prototype.getYear() zwraca zamiast tego rok pomniejszony o 1900 rok, zgodnie z wymogami standardów ECMAScript.

Podczas migracji skryptu do V8 zawsze używaj funkcji Date.prototype.getFullYear(), która zwraca 4-cyfrowy rok niezależnie od daty.

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

ECMAScript zabrania używania niektórych 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 V8 nie używaj w nazwach zmiennych ani funkcji zastrzeżonych słów kluczowych. Zmień nazwę zmiennej lub funkcji, aby uniknąć używania nazwy słowa kluczowego. Najczęściej używane nazwy słów kluczowych to class, importexport.

Unikaj ponownego przypisywania zmiennych const

W oryginalnym środowisku wykonawczym Rhino możesz zadeklarować zmienną za pomocą const, co oznacza, że wartość symbolu nigdy się nie zmienia, a przyszłe przypisania do symbolu 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 V8 nie próbuj ponownie przypisywać wartości zmiennej 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.

To niestandardowe rozszerzenie do ECMAScript umożliwia projektom Apps Script bezpośrednie używanie składni XML.

Podczas przenoszenia skryptu do V8 unikaj używania bezpośrednich literałów XML lub obiektu XML.

Zamiast tego użyj interfejsu XmlService do analizowania kodu 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 iteracyjnych za pomocą funkcji __iterator__.

W JavaScript 1.7 dodano funkcję, która umożliwia dodanie niestandardowego iteratora do dowolnej klasy przez zadeklarowanie funkcji __iterator__ w prototypie tej klasy. Funkcja ta została też dodana do środowiska wykonawczego Rhino w Apps Script, aby ułatwić pracę deweloperom. Ta funkcja nigdy jednak nie była częścią standardu ECMA-262 i została usunięta z silników JavaScript zgodnych z ECMAScript. Skrypty korzystające z V8 nie mogą używać tej konstrukcji iteracyjnej.

Podczas przenoszenia skryptu do V8 unikaj funkcji __iterator__ do tworzenia niestandardowych iteratorów. Zamiast tego użyj przeglądaczy ECMAScript 6.

Rozważ tę konstrukcję tablicy:

// 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 utworzyć iterator w środowisku Rhino i jak utworzyć zastępczy iterator w środowisku 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 klauzul warunkowych w blokach łapacza

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

Podczas migracji skryptu do V8 przenieś wszystkie warunki instrukcji catch do ciała instrukcji 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
  }
}

Unikaj korzystania z Object.prototype.toSource()

Wersja JavaScript 1.3 zawierała metodę Object.prototype.toSource(), która nigdy nie była częścią żadnego standardu ECMAScript. Nie jest ona obsługiwana w środowisku wykonawczym V8.

Podczas przenoszenia skryptu do V8 usuń z kodu wszystkie wystąpienia funkcji Object.prototype.toSource().

Inne różnice

Oprócz wymienionych powyżej niezgodności, które mogą powodować błędy skryptu, istnieje kilka innych różnic, które, jeśli nie zostaną poprawione, mogą skutkować nieoczekiwanym działaniem skryptu w czasie wykonywania w V8.

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

Dostosowywanie formatu daty i godziny do ustawień regionalnych

Metody Date 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 do niego są ignorowane.

W czasie wykonywania V8 domyślnym formatem jest krótki format, a przekazywane parametry są obsługiwane zgodnie ze standardem ECMA (szczegóły znajdziesz w toLocaleDateString() dokumentacji).

Podczas migracji skryptu do V8 przetestuj i dostosuj oczekiwania kodu dotyczące danych wyjściowych metod dotyczących daty i czasu w zależności od lokalizacji:

// 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 korzystania z Error.fileNameError.lineNumber

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

Podczas migracji skryptu do V8 usuń wszelkie zależności od funkcji Error.fileNameError.lineNumber.

Możesz też użyć polecenia Error.prototype.stack. Ten stos jest również niestandardowy, ale jest obsługiwany zarówno w Rhino, jak i V8. Format ścieżki wywołań generowanej przez obie 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)
      

Dostosowywanie obsługi obiektów typu enum zamienianych na ciągi znaków

W pierwotnym środowisku Rhino wywołanie metody JavaScript JSON.stringify() na obiekcie enum zwraca tylko {}.

W V8 ta sama metoda zastosowana do obiektu typu enum zwraca nazwę typu.

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 nieokreślonych parametrów

W pierwotnej wersji Rhino przekazanie wartości undefined do metody jako parametru powodowało przekazanie do niej ciągu znaków "undefined".

W V8 przekazanie do metody wartości undefined jest równoznaczne z przekazaniem wartości null.

Podczas migracji skryptu do V8 przetestuj i dostosuj oczekiwania kodu dotyczące parametrów 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.

Dostosowywanie obsługi globalnego this

Środowisko wykonawcze Rhino definiuje domyślny kontekst specjalny dla skryptów, które go używają. Kod skryptu jest wykonywany w tym domyślnym kontekście, który różni się od rzeczywistego kontekstu globalnego.this Oznacza to, że odwołania do „globalnego this” w kodzie są w rzeczywistości przeliczane w ramach specjalnego kontekstu, który zawiera tylko kod i zmienną zdefiniowane w skrypcie. Z użycia funkcji this są wykluczone wbudowane usługi Apps Script i obiekty ECMAScript. 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ług Apps Script i wbudowanych funkcji ECMAScript, takich jak MathDate.

Podczas migracji skryptu do V8 przetestuj i dostosuj oczekiwania kodu dotyczące użycia funkcji this w kontekście globalnym. W większości przypadków różnice są widoczne tylko wtedy, gdy kod analizuje klucze lub nazwy właściwości obiektu globalnego 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));
}

Dostosowanie obsługi instanceof w bibliotekach

Użycie funkcji instanceof w bibliotece na obiekcie przekazywanym jako parametr w funkcji z innego projektu może spowodować fałszywie wyniki 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 działający w V8 używa Twojego skryptu jako biblioteki, sprawdź, czy Twój skrypt używa parametru instanceof w przypadku parametru, który zostanie przekazany z innego projektu. Dostosuj użycie instanceof i użyj innych dostępnych alternatyw w zależności od przypadku użycia.

W przypadku funkcji a instanceof b możesz użyć konstruktora funkcji a, gdy nie musisz przeszukiwać całego łańcucha prototypów, tylko sprawdzić konstruktor. Użycie: a.constructor.name == "b"

Rozważmy projekt A i projekt B, w którym projekt A używa projektu B jako biblioteki.

//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ą alternatywą jest wprowadzenie w projekcie głównym funkcji, która sprawdza instanceof, oraz przekazywanie tej funkcji oprócz innych parametrów podczas wywoływania funkcji biblioteki. Przekazaną funkcję można następnie wykorzystać do sprawdzenia wartości 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);
}
      

Dostosowywanie przekazywania zasobów nieudostępnionych do bibliotek

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

W środowisku wykonawczym Rhino przekazanie niewspółdzielonego zasobu nie zadziała. Biblioteka używa wtedy własnego zasobu.

W czasie wykonywania V8 przekazywanie do biblioteki zasobu, który nie jest współdzielony, działa. Biblioteka korzysta z przekazanego niewspólnego zasobu.

Nie przekazuj zasobów nieudostępnionych jako parametry funkcji. Zawsze deklaruj zasoby nieudostępnione 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ólna.

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

Aktualizowanie dostępu do samodzielnych skryptów

Aby skrypty samodzielne działające w środowisku wykonawczym V8 działały prawidłowo, musisz przyznać użytkownikom co najmniej uprawnienia do wyświetlania skryptu.