Migracja skryptów do środowiska wykonawczego V8

Jeśli masz już skrypt korzystający ze środowiska wykonawczego Rhino i chcesz korzystać ze składni i funkcji V8, musisz przenieść go do wersji V8.

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

Występuje jednak niewielki zestaw niezgodności i innych różnic, które mogą spowodować awarię skryptu lub nieoczekiwane działanie po włączeniu środowiska wykonawczego V8. Podczas migracji skryptu do wersji 8 musisz przeszukać projekt skryptu pod kątem tych problemów i poprawić wszystkie znalezione błędy.

Procedura migracji do wersji 8

Aby przenieść skrypt do wersji V8, wykonaj następujące czynności:

  1. Włącz środowisko wykonawcze V8 dla skryptu.
  2. Uważnie sprawdź niezgodność wymienione poniżej. Sprawdź skrypt i sprawdź, czy nie występują jakieś rozbieżności. Jeśli je występują, dostosuj kod skryptu w taki sposób, aby je usunąć lub uniknąć problemu.
  3. Zapoznaj się uważnie z pozostałymi różnicami wymienionymi poniżej. Sprawdź skrypt i ustal, czy któraś z wymienionych różnic wpływa na zachowanie kodu. Popraw działanie skryptu.
  4. Po usunięciu wszelkich wykrytych niezgodności lub innych różnic możesz zacząć aktualizować kod, aby używać składni V8 i innych funkcji zgodnie z potrzebami.
  5. Po wprowadzeniu poprawek w kodzie dokładnie przetestuj go, by mieć pewność, że działa prawidłowo.
  6. Jeśli skrypt jest aplikacją internetową lub opublikowanym dodatkiem, musisz utworzyć nową wersję skryptu z dostosowaniem wersji V8. Aby udostępnić użytkownikom wersję V8, musisz ponownie opublikować skrypt z tą wersją.

Niezgodności

Oryginalne środowisko wykonawcze Apps Script oparte na Rhino zezwalało na kilka niestandardowych zachowań ECMAScript. Wersja 8 jest zgodna ze standardami, więc te działania nie są obsługiwane po migracji. Jeśli nie rozwiążesz tych problemów, po włączeniu środowiska wykonawczego V8 pojawią się błędy lub nieprawidłowe działanie skryptu.

Poniższe sekcje zawierają opis każdego z tych działań oraz czynności, jakie musisz wykonać, aby poprawić kod skryptu podczas migracji do wersji 8.

Unikaj: for each(variable in object)

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

Podczas migracji skryptu do wersji 8 unikaj używania instrukcji for each (variable in object).

Zamiast tego użyj parametru 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 pierwotnym środowisku wykonawczym Rhino funkcja Date.prototype.getYear() zwraca lata dwucyfrowe dla lat 1900–1999, a w przypadku innych dat – lata 4-cyfrowe, tak jak w języku JavaScript 1.2 i starszych.

W środowisku wykonawczym V8 Date.prototype.getYear() zwraca rok minus 1900, zgodnie ze standardami ECMAScript.

Podczas migracji skryptu do wersji V8 zawsze używaj witryny Date.prototype.getFullYear(), która zwraca rok w formacie 4-cyfrowym niezależnie od daty.

Unikaj stosowania zarezerwowanych słów kluczowych jako nazw

ECMAScript nie zezwala na używanie 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 ich używa w kodzie, musisz zmienić nazwy funkcji lub zmiennych.

Podczas migracji skryptu do V8 unikaj nadawania nazw zmiennym i funkcjom za pomocą jednego z zarezerwowanych słów kluczowych. Zmień nazwę dowolnej zmiennej lub funkcji, aby nie używać nazwy słowa kluczowego. Typowe zastosowania słów kluczowych jako nazw to class, import i export.

Unikaj ponownego przypisywania const zmiennych

W oryginalnym środowisku wykonawczym Rhino możesz zadeklarować zmienną za pomocą funkcji const, co oznacza, że wartość symbolu nigdy się nie zmieni, a przyszłe przypisania do tego symbolu będą ignorowane.

W nowym środowisku wykonawczym V8 słowo kluczowe const jest zgodne ze standardem i przypisanie go do zmiennej zadeklarowanej jako const powoduje wystąpienie błędu 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 ECMAScript umożliwia projektom Apps Script bezpośrednie używanie składni XML.

Podczas migracji skryptu do V8 unikaj bezpośrednich literałów XML ani obiektu XML.

Zamiast tego do analizy XML użyj usługi 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
      

Nie twórz niestandardowych funkcji iteratora za pomocą __iterator__

W języku JavaScript 1.7 dodano funkcję pozwalającą na dodawanie do dowolnych klas niestandardowego iteratora przez zadeklarowanie funkcji __iterator__ w prototypie tej klasy. Z uwagi na deweloperów dodaliśmy ją też do środowiska wykonawczego Apps Script Rhino. Ta funkcja nigdy jednak nie wchodziła w skład standardu ECMA-262 i została usunięta z mechanizmów JavaScript zgodnych z ECMAScript. Skrypty korzystające z V8 nie mogą korzystać z tej konstrukcji iteratora.

Gdy przenosisz skrypt do wersji V8, nie używaj funkcji __iterator__ do tworzenia niestandardowych iteratorów. Zamiast tego używaj iteratorów ECMAScript 6.

Rozważ taką 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 można skonstruować iterator w środowisku wykonawczym Rhino i jak utworzyć iterator zastępczy 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 catch

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

Podczas migracji skryptu do V8 przenieś wszelkie warunkowe elementy typu catch w treści przechwytywania:

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

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

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

Inne różnice

Oprócz powyższych niezgodności, które mogą powodować awarie skryptów, występuje kilka innych różnic, które – jeśli ich nie naprawisz, mogą powodować nieoczekiwane działanie skryptu wykonawczego V8.

W kolejnych sekcjach znajdziesz informacje o tym, jak zaktualizować kod skryptu, by uniknąć tych niespodziewanych niespodzianek.

Dostosuj formatowanie daty i godziny specyficzne dla języka

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

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

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

Podczas migracji skryptu do wersji 8 przetestuj kod i dostosuj go w odniesieniu do danych wyjściowych generowanych przez metody daty i godziny w poszczególnych językach:

// 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 aplikacji Error.fileName i Error.lineNumber

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

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

Możesz też użyć komponentu Error.prototype.stack. Ten stos również jest niestandardowy, ale obsługiwany zarówno w wersji Rhino, jak i V8. Format zrzutu stosu generowanego przez obie platformy nieco się różni:

// 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 wyliczeniowych w formie ciągów znaków

W pierwotnym środowisku wykonawczym Rhino użycie metody JavaScript JSON.stringify() w obiekcie wyliczenia zwraca tylko wartość {}.

W wersji 8 użycie tej samej metody w przypadku obiektu wyliczenia przywraca nazwę wyliczenia.

Podczas migracji skryptu do wersji V8 przetestuj i dostosuj swoje oczekiwania w zakresie danych wyjściowych JSON.stringify() na obiektach wyliczeniowych:

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

Dostosuj obsługę niezdefiniowanych parametrów

W pierwotnym środowisku wykonawczym Rhino przekazanie metody undefined jako parametru spowodowało przekazanie do tej metody ciągu znaków "undefined".

W wersji 8 przekazanie metody undefined do metod jest równoważne z przekazaniem metody null.

Podczas migracji skryptu do wersji 8 przetestuj i dostosuj oczekiwania dotyczące kodu w odniesieniu do 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.

Dostosuj obsługę globalnego atrybutu this

Środowisko wykonawcze Rhino definiuje niejawny specjalny kontekst dla skryptów, które z niego korzystają. Kod skryptu uruchamia się w tym niejawnym kontekście, w innym przypadku niż rzeczywisty globalny this. Oznacza to, że odwołania do „globalnego elementu this” w kodzie faktycznie wpływają na specjalny kontekst, który zawiera tylko kod i zmienne zdefiniowane w skrypcie. Wbudowane usługi Apps Script i obiekty ECMAScript są wykluczone z tego zastosowania this. Sytuacja była podobna do tej struktury JavaScriptu:

// 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 wersji 8 niejawny kontekst specjalny jest usuwany. Zmienne globalne i funkcje zdefiniowane w skrypcie są umieszczane w kontekście globalnym obok wbudowanych usług Apps Script i wbudowanych aplikacji ECMAScript, takich jak Math i Date.

Podczas migracji skryptu do wersji 8 przetestuj i dostosuj oczekiwania dotyczące kodu w odniesieniu do użycia 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 globalnego obiektu 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 sposób obsługi elementu instanceof w bibliotekach

Użycie w bibliotece instanceof w obiekcie, który jest przekazywany jako parametr w funkcji z innego projektu, może zwrócić wyniki fałszywie negatywne. W środowisku wykonawczym V8 projekt i jego biblioteki są uruchamiane w różnych kontekstach wykonywania, dlatego mają różne globalne i łańcuchy prototypów.

Pamiętaj, że dzieje się tak tylko wtedy, gdy Twoja biblioteka używa instanceof w obiekcie, który nie został utworzony w Twoim projekcie. Powinno działać zgodnie z oczekiwaniami – niezależnie od tego, czy jest on utworzony w tym samym czy innym skrypcie, co obiekt utworzony w projekcie.

Jeśli projekt uruchomiony w wersji 8 używa skryptu jako biblioteki, sprawdź, czy skrypt używa instanceof w parametrze przekazywanym z innego projektu. Dostosuj wykorzystanie instanceof i użyj innych możliwych rozwiązań alternatywnych zgodnie ze swoimi potrzebami.

Alternatywą dla a instanceof b może być użycie konstruktora a w sytuacjach, gdy nie ma potrzeby przeszukiwania całego łańcucha prototypu, a jedynie sprawdzania konstruktora. Użycie: a.constructor.name == "b"

Rozważmy Projekt A i Projekt B, w których 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
}

Innym rozwiązaniem może być wprowadzenie funkcji, która sprawdza właściwość instanceof w głównym projekcie i przekazuje ją razem z innymi parametrami przy jej wywoływaniu. Następnie można użyć przekazanej funkcji, aby sprawdzić element 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 nieudostępnionego zasobu ze skryptu głównego do biblioteki działa inaczej w środowisku wykonawczym V8.

W środowisku wykonawczym Rhino przekazywanie nieudostępnionego zasobu nie zadziała. Biblioteka używa własnego zasobu.

W środowisku wykonawczym V8 przekazywanie do biblioteki nieudostępnionych zasobów działa. Biblioteka korzysta z przekazanego zasobu, który nie jest udostępniony.

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

Rozważmy Projekt A i Projekt B, w których Projekt A wykorzystuje Projekt B jako bibliotekę. W tym przykładzie PropertiesService nie jest udostępnionym 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 przynajmniej uprawnienia do wyświetlania skryptu, aby jego aktywatory działały prawidłowo.