Rhino ランタイムを使用している既存のスクリプトで V8 構文と機能を使用したい場合は、スクリプトを V8 に移行する必要があります。
Rhino ランタイムを使用して記述されたほとんどのスクリプトは、調整なしで V8 ランタイムを使用して動作できます。多くの場合、V8 構文と機能をスクリプトに追加する前提条件は、V8 ランタイムを有効にすることだけです。
ただし、V8 ランタイムを有効にした後にスクリプトが失敗したり、予期しない動作をしたりする可能性がある、非互換性とその他の違いがいくつかあります。スクリプトを V8 を使用するように移行する際は、スクリプト プロジェクトでこれらの問題を検索し、見つかった問題を修正する必要があります。
V8 の移行手順
スクリプトを V8 に移行する手順は次のとおりです。
- スクリプトの V8 ランタイムを有効にする。
- 以下の互換性のない機能をよく確認してください。スクリプトを確認し、互換性がないかどうかを判断します。互換性が 1 つ以上ある場合は、スクリプトコードを調整して問題を解消するか回避します。
- 以下のその他の違いをよく確認してください。スクリプトを確認し、リストに記載されている違いがコードの動作に影響しているかどうかを判断します。スクリプトを調整して動作を修正します。
- 検出された非互換性やその他の違いを修正したら、必要に応じて V8 構文やその他の機能を使用するようにコードを更新できます。
- コードの調整が完了したら、スクリプトを徹底的にテストして、想定どおりに動作することを確認します。
- スクリプトがウェブアプリまたは公開済みのアドオンである場合は、V8 の調整を加えてスクリプトの新しいバージョンを作成する必要があります。V8 バージョンをユーザーが利用できるようにするには、このバージョンでスクリプトを再公開する必要があります。
非互換性
残念ながら、元の Rhino ベースの Apps Script ランタイムでは、いくつかの標準外の 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 年の年は 2 桁の年を返しますが、それ以外の日付は 4 桁の年を返します。これは JavaScript 1.2 以前の動作です。
V8 ランタイムでは、Date.prototype.getYear()
は ECMAScript 標準で求められるように、年から 1900 を引いた値を返します。
スクリプトを V8 に移行する場合は、常に Date.prototype.getFullYear()
を使用してください。これは、日付に関係なく 4 桁の年を返します。
予約済みキーワードを名前として使用しないでください
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__
関数を宣言することで、任意のクラスにカスタム イテレータを追加できる機能が追加されました。これは、デベロッパーの利便性のために Apps Script の 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); } |
条件付きの 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 には、ECMAScript 標準の一部ではなかった Object.prototype.toSource() メソッドが含まれていました。V8 ランタイムではサポートされていません。
スクリプトを V8 に移行する場合は、コードから Object.prototype.toSource() の使用をすべて削除してください。
その他の相違点
スクリプトの失敗につながる上記の非互換性に加えて、修正しないと予期しない V8 ランタイム スクリプトの動作につながる可能性があるその他の違いがいくつかあります。
以降のセクションでは、このような予期しない事態を回避するためにスクリプト コードを更新する方法について説明します。
言語 / 地域固有の日付と時刻の形式を調整する
Date
メソッド toLocaleString()
、toLocaleDateString()
、toLocaleTimeString()
は、Rhino と比較して V8 ランタイムで動作が異なります。
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
を使用することもできます。このスタックも標準ではありませんが、Rhino と V8 の両方でサポートされています。2 つのプラットフォームで生成されるスタック トレース形式は若干異なります。
// 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()
メソッドを使用すると、{}
のみが返されます。
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
」への参照は、実際にはスクリプトで定義されたコードと変数のみを含む特別なコンテキストに評価されます。組み込みの 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"
プロジェクト A とプロジェクト B について考えてみましょう。プロジェクト A はプロジェクト 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 ランタイムで実行されるスタンドアロン スクリプトの場合、スクリプトのトリガーが正しく機能するように、ユーザーにスクリプトへの閲覧アクセス権を少なくとも付与する必要があります。