Среда выполнения Rhino прекратит работу 31 января 2026 г. Если у вас есть существующий скрипт, использующий среду выполнения Rhino, вам необходимо перенести его в V8.
Часто единственным условием для добавления синтаксиса и функций V8 в скрипт является включение среды выполнения V8 . Однако существует ряд несовместимостей и других различий , которые могут привести к сбоям в работе скрипта или его непредвиденному поведению в среде выполнения V8. При переносе скрипта для использования V8 необходимо выполнить поиск этих проблем в проекте скрипта и исправить их.
Процедура миграции V8
Чтобы перенести скрипт в V8, выполните следующую процедуру:
- Включите среду выполнения V8 для скрипта.
runtimeVersion
можно проверить с помощью манифеста проекта Apps Script. - Внимательно изучите следующие несовместимости . Проверьте свой скрипт на наличие каких-либо несовместимостей; если присутствует одна или несколько несовместимостей, скорректируйте код скрипта, чтобы устранить или избежать проблемы.
- Внимательно изучите следующие различия . Проверьте свой скрипт, чтобы определить, влияют ли какие-либо из перечисленных различий на поведение вашего кода. Скорректируйте скрипт, чтобы исправить это поведение.
- После устранения всех обнаруженных несовместимостей или других различий вы можете приступить к обновлению своего кода для использования синтаксиса V8 и других функций .
- После завершения корректировки кода тщательно протестируйте свой скрипт, чтобы убедиться, что он работает так, как и ожидалось.
- Если ваш скрипт представляет собой веб-приложение или опубликованное дополнение , необходимо создать новую версию скрипта с изменениями V8 и указать для развертывания новую версию. Чтобы сделать версию V8 доступной пользователям, необходимо повторно опубликовать скрипт с этой версией.
- Если ваш скрипт используется как библиотека, создайте новую версию развёртывания вашего скрипта. Сообщите об этой новой версии всем скриптам и пользователям, использующим вашу библиотеку, попросив их обновиться до версии с поддержкой V8. Убедитесь, что все старые версии вашей библиотеки на базе Rhino больше не используются и не доступны.
- Убедитесь, что ни один экземпляр вашего скрипта не работает в устаревшей среде выполнения Rhino. Убедитесь, что все развёртывания связаны с версией, работающей в V8. Архивируйте старые развёртывания. Проверьте все версии и удалите те, которые не используют среду выполнения V8.
Несовместимости
К сожалению, исходная среда выполнения Apps Script на базе Rhino допускала несколько нестандартных вариантов поведения ECMAScript. Поскольку V8 соответствует стандартам, эти варианты поведения не поддерживаются после миграции. Если не устранить эти проблемы, после включения среды выполнения V8 возникнут ошибки или возникнут сбои в работе скрипта.
В следующих разделах описывается каждое из этих поведений и шаги, которые необходимо предпринять для исправления кода скрипта во время миграции на V8.
Избегайте 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 для удобства разработчиков. Однако эта возможность никогда не была частью стандарта ECMA-262 и была удалена из совместимых с ECMAScript JavaScript-движков. Скрипты, использующие 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); } |
Избегайте условных предложений о перехвате
Среда выполнения 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
В среде выполнения V8 стандартный объект JavaScript Error
не поддерживает fileName
или lineNumber
в качестве параметров конструктора или свойств объекта.
При переносе скрипта в V8 удалите любую зависимость от Error.fileName
и Error.lineNumber
.
Альтернативой является использование стека Error.prototype.stack
. Этот стек также нестандартен, но поддерживается в 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) |
Настройте обработку строковых перечислений объектов
В исходной среде выполнения Rhino использование метода JavaScript JSON.stringify()
для объекта enum возвращает только {}
.
В V8 использование того же метода для объекта перечисления возвращает имя перечисления.
При переносе скрипта в V8 протестируйте и скорректируйте ожидания вашего кода относительно вывода JSON.stringify()
для объектов 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" |
Настройте обработку неопределенных параметров
В исходной среде выполнения 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
» в коде фактически относятся к специальному контексту, который содержит только код и переменные, определённые в скрипте. Встроенные службы Apps Script и объекты 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 неявный специальный контекст убран. Глобальные переменные и функции, определённые в скрипте, помещаются в глобальный контекст, рядом со встроенными службами Apps Script и встроенными функциями 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"
Рассмотрим проект А и проект Б, где проект А использует проект Б в качестве библиотеки.
//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 и проект B, где проект A использует проект 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, необходимо предоставить пользователям как минимум доступ для просмотра скрипта, чтобы триггеры скрипта работали правильно.