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.

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 przenoszenia skryptu do wersji 8 musisz wyszukać w projekcie skryptu i poprawić wszystkie znalezione błędy.

Procedura migracji 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. Zmień 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

Oryginalne środowisko wykonawcze Apps Script oparte na Rhino zezwalało na kilka niestandardowych działań ECMAScript. Ponieważ V8 jest zgodna 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 JavaScriptu 1.6 i usunięta na rzecz 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 przenoszenia 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 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 V8 unikaj nadawania zmiennym lub funkcjom nazw zawierających zarezerwowane słowa kluczowe. Zmień nazwy zmiennych lub funkcji, aby nie używać 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 słowo kluczowe const jest zgodne ze standardem, a przypisanie do zmiennej zadeklarowanej jako const powoduje błąd 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 korzystanie z 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 iteratora za pomocą __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 ze standardem ECMAScript. Skrypty używające V8 nie mogą używać tej konstrukcji iteracji.

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 warunkowych klauzul przechwytywania

Ś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 on obsługiwany w środowisku wykonawczym V8.

Podczas przenoszenia skryptu do V8 usuń z kodu wszystkie wystąpienia 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 następnych sekcjach wyjaśniamy, jak zaktualizować kod skryptu, aby uniknąć tych 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 danym języku:

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

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

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 elementu this” w kodzie oceniają się w specjalnym kontekście, który zawiera tylko kod i zmienne zdefiniowane w skrypcie. Z tego użycia this są wykluczone wbudowane usługi Apps Script i obiekty ECMAScript. 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 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 sprawdza 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));
}

Dostosuj obsługę pliku 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 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 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. Wykorzystanie: 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ą możliwością 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. 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);
}
      

Dostosowywanie przekazywania zasobów nieudostępnionych do bibliotek

Przekazywanie niedzielonemu zasóbowi 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 wtedy własnego zasobu.

W środowisku wykonawczym V8 przesyłanie nieudostępnionych zasobów do biblioteki 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); }

Zaktualizuj dostęp 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.