Migracja skryptów do środowiska wykonawczego V8

Jeśli masz już skrypt używający środowiska wykonawczego Rhino i chcesz wykorzystać składnię i funkcje V8, musisz przeprowadzić migrację do wersji V8.

Większość skryptów napisanych w środowisku wykonawczym Rhino może działać w środowisku V8 bez dostosowywania. Często jedynym warunkiem dodania do skryptu składni i funkcji V8 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. Gdy przenosisz skrypt do wersji 8, musisz wyszukać w projekcie skryptu i poprawić wszystkie znalezione błędy.

Procedura migracji V8

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

  1. Włącz środowisko wykonawcze V8 dla skryptu.
  2. Dokładnie sprawdź niezgodności wymienione poniżej. Sprawdź skrypt, aby określić, czy występują jakieś niezgodności. Jeśli występują co najmniej 1 niezgodność, dostosuj kod skryptu, aby usunąć problem lub go uniknąć.
  3. Uważnie zapoznaj się z innymi różnicami wymienionymi poniżej. Sprawdź swój skrypt, aby określić, czy któraś z wymienionych różnic ma wpływ na działanie Twojego kodu. Dostosuj skrypt, aby rozwiązać ten problem.
  4. Po wyeliminowaniu wszelkich wykrytych niezgodności i innych rozbieżności możesz rozpocząć aktualizowanie kodu, aby korzystać ze składni V8 i innych funkcji zgodnie z potrzebami.
  5. Po zakończeniu korekt kodu dokładnie przetestuj skrypt, aby upewnić się, że działa on zgodnie z oczekiwaniami.
  6. Jeśli Twój skrypt jest aplikacją internetową lub opublikowanym dodatkiem, musisz utworzyć nową wersję skryptu z korektami 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 działań ECMAScript. Ponieważ V8 jest zgodny ze standardami, te zachowania nie są obsługiwane po migracji. Jeśli nie rozwiążesz tych problemów, po włączeniu środowiska wykonawczego V8 wystąpią błędy lub działanie skryptu nie będzie możliwe.

W poniższych sekcjach opisano wszystkie te zachowania i kroki, które 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 przenoszenia skryptu do wersji 8 unikaj używania instrukcji for each (variable in object).

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 pierwotnym środowisku wykonawczym Rhino Date.prototype.getYear() zwraca lata 2-cyfrowe dla lat 1900–1999, a dla pozostałych dat zwracany jest rok 4-cyfrowy, co było stosowane w JavaScripcie w wersji 1.2 i starszych.

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

Podczas przenoszenia skryptu do wersji 8 zawsze używaj Date.prototype.getFullYear(), która zwraca rok bez względu na datę.

Unikaj używania zarezerwowanych słów kluczowych jako nazw

ECMAScript zabrania używania określonych zarezerwowanych słów kluczowych w nazwach funkcji i zmiennych. Środowisko wykonawcze Rhino zezwala na wiele z tych słów, więc jeśli są one używane w Twoim kodzie, musisz zmienić nazwy funkcji lub zmiennych.

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

Unikaj ponownego przypisywania const zmiennych

W pierwotnym środowisku wykonawczym Rhino możesz zadeklarować zmienną za pomocą parametru const. Oznacza to, że wartość symbolu nigdy się nie zmienia, a przyszłe przypisania do symbolu będą ignorowane.

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

Podczas przenoszenia skryptu do wersji 8 nie próbuj ponownie przypisać 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 pozwala projektom Apps Script bezpośrednio używać składni XML.

Podczas przenoszenia skryptu do wersji 8 unikaj używania bezpośrednich literałów XML ani obiektu XML.

Zamiast tego do przeanalizowania kodu 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 w wersji 1.7 dodaliśmy funkcję umożliwiającą dodanie niestandardowego iteratora do dowolnych klas przez zadeklarowanie funkcji __iterator__ w prototypie tej klasy. Dla wygody programistów ta funkcja została też dodana do środowiska wykonawczego Rhino Apps Script. Ta funkcja nigdy nie była częścią standardu ECMA-262 i została usunięta z silników JavaScriptu zgodnych z ECMAScript. Skrypty używające V8 nie mogą używać tej konstrukcji iteracji.

W przypadku przenoszenia skryptu do wersji 8 unikaj funkcji __iterator__ do tworzenia niestandardowych iteratorów. Zamiast tego używaj iteratorów 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 utworzyć iterator w środowisku wykonawczym Rhino oraz 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 przechwytywania

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

Podczas przenoszenia skryptu do wersji 8 przenieś wszystkie warunki przechwytywania do treści skryptu:

// 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ł metodę Object.prototype.toSource(), która nigdy nie była częścią żadnego standardu ECMAScript. Nie jest on obsługiwany w środowisku wykonawczym V8.

Gdy przenosisz skrypt do wersji 8, usuń z kodu wszelkie użycie funkcji Object.prototype.toSource().

Inne różnice

Oprócz powyższych niezgodności, które mogą powodować błędy skryptu, istnieje kilka innych różnic, które – jeśli nie zostaną naprawione, mogą spowodować nieoczekiwane działanie skryptu środowiska wykonawczego V8.

W kolejnych sekcjach dowiesz się, jak zaktualizować kod skryptu, by uniknąć tych nieoczekiwanych niespodzianek.

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

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 przekazywane parametry są ignorowane.

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

Podczas przenoszenia skryptu do wersji 8 przetestuj i dostosuj swoje oczekiwania wobec kodu, biorąc pod uwagę dane wyjściowe metod dat i godzin w różnych 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 używania atrybutów Error.fileName i Error.lineNumber

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

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

Możesz też użyć polecenia 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 użycie metody JavaScript JSON.stringify() w obiekcie wyliczenia zwraca tylko {}.

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

Podczas przenoszenia skryptu do wersji 8 przetestuj i dostosuj oczekiwania kodu dotyczące danych wyjściowych JSON.stringify() w 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"

Dostosowywanie obsługi niezdefiniowanych parametrów

W pierwotnym środowisku wykonawczym Rhino przekazanie jako parametru parametru undefined do metody skutkował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 przenoszenia skryptu do wersji 8 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.

Dostosuj obsługę globalnego zasobu this

Środowisko wykonawcze Rhino definiuje niejawny specjalny kontekst dla skryptów, które z niego korzystają. Kod skryptu działa w tym niejawnym kontekście, inny niż rzeczywisty globalny this. Oznacza to, że odwołania do „globalnego elementu this” w kodzie wpływają na specjalny kontekst, który zawiera tylko kod i zmienne zdefiniowane w skrypcie. Wbudowane usługi Apps Script i obiekty ECMAScript nie są uwzględniane w zasadzie this. Sytuacja była podobna do 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 V8 niejawny 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 Math i Date.

Podczas przenoszenia skryptu do wersji 8 przetestuj i dostosuj swoje oczekiwania wobec kodu w zakresie wykorzystania 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 obsługę pliku instanceof w bibliotekach

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

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

Jeśli projekt uruchomiony w wersji 8 używa Twojego skryptu jako biblioteki, sprawdź, czy używa on parametru instanceof w parametrze, który będzie przekazywany z innego projektu. Dostosuj użycie właściwości instanceof i skorzystaj z innych, dopasowanych do Twojego przypadku użycia alternatywnych rozwiązań.

Jednym z rozwiązań a instanceof b może być użycie konstruktora a w sytuacjach, gdy nie trzeba przeszukiwać całego łańcucha prototypów i tylko sprawdzać konstruktor. 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 obiekt instanceof w głównym projekcie i przekazuje tę funkcję razem z innymi parametrami przy wywoływaniu funkcji biblioteki. Przekazanej funkcji 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 nieudostępnionych zasobów z głównego skryptu 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 nieudostępnionego zasobu.

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.

Weźmy pod uwagę Projekt A i Projekt B, w ramach którego Projekt A wykorzystuje Projekt B jako bibliotekę. W tym przykładzie zasób PropertiesService nie jest udostępniany.

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

Aby aktywatory skryptu działały prawidłowo, w przypadku samodzielnych skryptów działających w środowisku wykonawczym V8 musisz przyznać użytkownikom co najmniej uprawnienia do wyświetlania skryptu.