Если у вас есть сценарий, использующий среду выполнения Rhino, и вы хотите использовать синтаксис и функции V8, вы должны перенести этот сценарий на V8 .
Большинство сценариев, написанных с использованием среды выполнения Rhino, могут работать с использованием среды выполнения V8 без дополнительной настройки. Часто единственным предварительным условием для добавления синтаксиса и функций V8 в сценарий является включение среды выполнения V8 .
Однако существует небольшой набор несовместимостей и других отличий , которые могут привести к сбою или неожиданному поведению сценария после включения среды выполнения V8. При переносе сценария для использования V8 необходимо выполнить поиск этих проблем в проекте сценария и исправить все найденные.
Процедура перехода на V8
Чтобы перенести сценарий в V8, выполните следующую процедуру:
- Включите среду выполнения V8 для скрипта.
- Внимательно просмотрите несовместимости , перечисленные ниже. Изучите свой сценарий, чтобы определить, присутствуют ли какие-либо несовместимости; если присутствует одна или несколько несовместимостей, измените код сценария, чтобы устранить или избежать проблемы.
- Внимательно просмотрите другие отличия , перечисленные ниже. Изучите свой сценарий, чтобы определить, влияют ли какие-либо из перечисленных отличий на поведение вашего кода. Настройте свой сценарий, чтобы исправить поведение.
- После того, как вы исправите все обнаруженные несовместимости или другие различия, вы можете приступить к обновлению своего кода, чтобы использовать синтаксис V8 и другие функции по желанию.
- После завершения корректировки кода тщательно протестируйте свой сценарий, чтобы убедиться, что он ведет себя так, как ожидалось.
- Если ваш скрипт является веб-приложением или опубликованным дополнением , вы должны создать новую версию скрипта с корректировками 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')); } | // V8 runtime // Project A function testPassingNonSharedProperties() { PropertiesService.getScriptProperties() .setProperty('project', 'Project-A'); B.setScriptProperties(); // Prints: Project-A Logger.log(B.getScriptProperties( PropertiesService, 'project')); } |
Обновление доступа к автономным скриптам
Для автономных сценариев, работающих в среде выполнения V8, вам необходимо предоставить пользователям как минимум доступ к просмотру сценария, чтобы триггеры сценария работали правильно.