Миграция скриптов в среду выполнения V8

Оптимизируйте свои подборки Сохраняйте и классифицируйте контент в соответствии со своими настройками.

Если у вас есть сценарий, использующий среду выполнения Rhino, и вы хотите использовать синтаксис и функции V8, вы должны перенести этот сценарий на V8 .

Большинство сценариев, написанных с использованием среды выполнения Rhino, могут работать с использованием среды выполнения V8 без дополнительной настройки. Часто единственным предварительным условием для добавления синтаксиса и функций V8 в сценарий является включение среды выполнения V8 .

Однако существует небольшой набор несовместимостей и других отличий , которые могут привести к сбою или неожиданному поведению сценария после включения среды выполнения V8. При переносе сценария для использования V8 необходимо выполнить поиск этих проблем в проекте сценария и исправить все найденные.

Процедура перехода на V8

Чтобы перенести сценарий в V8, выполните следующую процедуру:

  1. Включите среду выполнения V8 для скрипта.
  2. Внимательно просмотрите несовместимости , перечисленные ниже. Изучите свой сценарий, чтобы определить, присутствуют ли какие-либо несовместимости; если присутствует одна или несколько несовместимостей, измените код сценария, чтобы устранить или избежать проблемы.
  3. Внимательно просмотрите другие отличия , перечисленные ниже. Изучите свой сценарий, чтобы определить, влияют ли какие-либо из перечисленных отличий на поведение вашего кода. Настройте свой сценарий, чтобы исправить поведение.
  4. После того, как вы исправите все обнаруженные несовместимости или другие различия, вы можете приступить к обновлению своего кода, чтобы использовать синтаксис V8 и другие функции по желанию.
  5. После завершения корректировки кода тщательно протестируйте свой сценарий, чтобы убедиться, что он ведет себя так, как ожидалось.
  6. Если ваш скрипт является веб-приложением или опубликованным дополнением , вы должны создать новую версию скрипта с корректировками V8. Чтобы сделать версию V8 доступной для пользователей, необходимо повторно опубликовать сценарий с этой версией.

Несовместимости

Первоначальная среда выполнения Apps Script на основе Rhino, к сожалению, допускала несколько нестандартных вариантов поведения ECMAScript. Поскольку версия 8 соответствует стандартам, такое поведение не поддерживается после миграции. Неспособность исправить эти проблемы приводит к ошибкам или нарушению поведения сценария после включения среды выполнения V8.

В следующих разделах описывается каждое из этих действий и шаги, которые необходимо предпринять для исправления кода сценария во время перехода на версию 8.

Избегайте for each(variable in object)

Оператор for each (variable in object) был добавлен в JavaScript 1.6 и удален в пользу for...of .

При переносе сценария на V8 избегайте использования операторов for each (variable in object) .

Вместо этого используйте 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);
}
      

Избегайте Date.prototype.getYear()

В оригинальной среде выполнения Rhino Date.prototype.getYear() возвращает две цифры года для годов с 1900 по 1999 и четыре цифры для других дат, что было поведением в JavaScript 1.2 и более ранних версиях.

В среде выполнения V8 Date.prototype.getYear() вместо этого возвращает год минус 1900, как того требуют стандарты ECMAScript.

При переносе сценария на V8 всегда используйте Date.prototype.getFullYear() , который возвращает четырехзначный год независимо от даты.

Избегайте использования зарезервированных ключевых слов в качестве имен

ECMAScript запрещает использование определенных зарезервированных ключевых слов в именах функций и переменных. Среда выполнения Rhino допускала многие из этих слов, поэтому, если они используются в вашем коде, вы должны переименовать свои функции или переменные.

При переносе сценария на V8 избегайте именования переменных или функций одним из зарезервированных ключевых слов . Переименуйте любую переменную или функцию, чтобы избежать использования имени ключевого слова. Обычное использование ключевых слов в качестве имен — class , import и export .

Избегайте переназначения const переменных

В исходной среде выполнения Rhino вы можете объявить переменную, используя const , что означает, что значение символа никогда не изменяется, а будущие назначения символу игнорируются.

В новой среде выполнения V8 ключевое слово const совместимо со стандартом, и его присвоение переменной, объявленной как const , приводит к ошибке TypeError: Assignment to constant variable .

При переносе скрипта на V8 не пытайтесь переназначить значение 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
      

Избегайте литералов XML и объекта XML

Это нестандартное расширение ECMAScript позволяет проектам Apps Script напрямую использовать синтаксис XML.

При переносе сценария на V8 избегайте использования прямых литералов XML или объекта XML .

Вместо этого используйте XmlService для анализа 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
      

Не создавайте пользовательские функции итератора, используя __iterator__

В JavaScript 1.7 добавлена ​​функция, позволяющая добавлять пользовательский итератор в любой класс путем объявления функции __iterator__ в прототипе этого класса; это также было добавлено в среду выполнения Rhino Apps Script для удобства разработчиков. Однако эта функция никогда не была частью стандарта ECMA-262 и была удалена из движков JavaScript, совместимых с ECMAScript. Скрипты, использующие V8, не могут использовать эту конструкцию итератора.

При переносе скрипта на V8 избегайте использования функции __iterator__ для создания собственных итераторов . Вместо этого используйте итераторы ECMAScript 6 .

Рассмотрим следующую конструкцию массива:

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

В следующих примерах кода показано, как можно создать итератор в среде выполнения Rhino и как создать замещающий итератор в среде выполнения 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);
}
      

Избегайте условных предложений catch

Среда выполнения V8 не поддерживает catch..if , поскольку они не соответствуют стандартам.

При переносе скрипта на V8 переместите все условные операторы catch внутрь тела 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
  }
}

Избегайте использования Object.prototype.toSource()

JavaScript 1.3 содержал метод Object.prototype.toSource() , который никогда не был частью какого-либо стандарта ECMAScript. Он не поддерживается в среде выполнения V8.

При переносе сценария на V8 удалите из кода любое использование Object.prototype.toSource() .

Другие отличия

В дополнение к вышеупомянутым несовместимостям, которые могут вызвать сбои сценариев, есть несколько других отличий, которые, если их не исправить, могут привести к непредвиденному поведению сценария среды выполнения V8.

В следующих разделах объясняется, как обновить код скрипта, чтобы избежать таких неожиданных сюрпризов.

Настройка форматирования даты и времени для локали

Методы Date toLocaleString() , toLocaleDateString() и toLocaleTimeString() ведут себя в среде выполнения V8 иначе, чем в Rhino.

В Rhino форматом по умолчанию является длинный формат , и любые передаваемые параметры игнорируются .

В среде выполнения V8 форматом по умолчанию является краткий формат, а передаваемые параметры обрабатываются в соответствии со стандартом ECMA (подробности см. в документации toLocaleDateString() ).

При переносе скрипта на V8 проверьте и скорректируйте ожидания вашего кода в отношении вывода методов даты и времени, зависящих от локали :

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

Избегайте использования Error.fileName и Error.lineNumber

В lineNumber V8 стандартный объект JavaScript Error не поддерживает имя fileName или номер строки в качестве параметров конструктора или свойств объекта.

При переносе скрипта на V8 удалите любую зависимость от Error.fileName и Error.lineNumber .

Альтернативой является использование Error.prototype.stack . Этот стек тоже нестандартен, но поддерживается как в Rhino, так и в V8. Формат трассировки стека, создаваемой двумя платформами, немного отличается:

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

Настройте обработку строковых объектов enum

В исходной среде выполнения Rhino использование метода JavaScript JSON.stringify() для объекта enum возвращает только {} .

В V8 использование того же метода для объекта перечисления возвращает имя перечисления.

При переносе вашего скрипта на V8 проверьте и скорректируйте ожидания вашего кода в отношении вывода JSON.stringify() для объектов перечисления :

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

Настройка обработки неопределенных параметров

В исходной среде выполнения Rhino передача undefined методу в качестве параметра приводила к передаче этому методу строки "undefined" .

В V8 передача undefined методам эквивалентна передаче null .

При переносе вашего скрипта на V8 проверьте и скорректируйте ожидания вашего кода в отношении 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.

Отрегулируйте обработку глобального this

Среда выполнения Rhino определяет неявный специальный контекст для скриптов, которые его используют. Код скрипта выполняется в этом неявном контексте, отличном от фактического глобального this . Это означает, что ссылки на "глобальное this " в коде фактически относятся к специальному контексту, который содержит только код и переменные, определенные в скрипте. Встроенные службы сценариев приложений и объекты ECMAScript исключены из этого использования this . Эта ситуация была похожа на эту структуру 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.
}();

В V8 неявный специальный контекст удален. Глобальные переменные и функции, определенные в сценарии, помещаются в глобальный контекст, рядом со встроенными службами сценариев приложений и встроенными функциями ECMAScript, такими как Math и Date .

При переносе вашего скрипта на V8 протестируйте и скорректируйте ожидания вашего кода относительно использования this в глобальном контексте. В большинстве случаев различия очевидны только в том случае, если ваш код проверяет ключи или имена свойств глобального объекта 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));
}

Настройте обработку instanceof в библиотеках

Использование instanceof в библиотеке для объекта, который передается в качестве параметра функции из другого проекта, может привести к ложным отрицательным результатам. В среде выполнения V8 проект и его библиотеки запускаются в разных контекстах выполнения и, следовательно, имеют разные глобальные переменные и цепочки прототипов.

Обратите внимание, что это только в том случае, если ваша библиотека использует instanceof для объекта, который не создан в вашем проекте. Использование его на объекте, созданном в вашем проекте, будь то в том же или другом скрипте внутри вашего проекта, должно работать должным образом.

Если проект, работающий на V8, использует ваш сценарий в качестве библиотеки, проверьте, использует ли ваш сценарий instanceof для параметра, который будет передан из другого проекта. Отрегулируйте использование instanceof и используйте другие возможные альтернативы в соответствии с вашим вариантом использования.

Одной из альтернатив для a instanceof b может быть использование конструктора a в тех случаях, когда вам не нужно искать всю цепочку прототипов и просто проверять конструктор. Использование: a.constructor.name == "b"

Рассмотрим проекты A и Project B, где Project A использует Project B в качестве библиотеки.

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

Другой альтернативой может быть введение функции, которая проверяет instanceof в основном проекте и передает функцию в дополнение к другим параметрам при вызове библиотечной функции. Затем переданную функцию можно использовать для проверки instanceof внутри библиотеки.

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

Настройте передачу необщих ресурсов в библиотеки

Передача неразделяемого ресурса из основного сценария в библиотеку в среде выполнения V8 работает иначе.

В среде выполнения Rhino передача ресурса, не являющегося общим, не будет работать. Вместо этого библиотека использует собственный ресурс.

В среде выполнения V8 передача неразделяемого ресурса в библиотеку работает. Библиотека использует переданный неразделяемый ресурс.

Не передавайте неразделяемые ресурсы в качестве параметров функции. Всегда объявляйте неразделяемые ресурсы в том же скрипте, который их использует.

Рассмотрим проекты A и Project B, где Project A использует Project B в качестве библиотеки. В этом примере PropertiesService является необщим ресурсом.

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

Обновление доступа к автономным скриптам

Для автономных сценариев, работающих в среде выполнения V8, вам необходимо предоставить пользователям как минимум доступ к просмотру сценария, чтобы триггеры сценария работали правильно.