如果您有使用 Rhino 執行階段的指令碼,而且想要使用 V8 語法和功能,則必須將指令碼遷移至 V8。
大多數使用 Rhino 執行階段編寫的指令碼都能透過 V8 執行階段運作,而不會進行調整。一般來說,在指令碼中加入 V8 語法和功能的唯一先決條件是啟用 V8 執行階段。
不過,在啟用 V8 執行階段後,仍有少數不相容和其他差異,可能導致指令碼失敗或出現非預期的行為。遷移指令碼以使用 V8 時,必須在指令碼專案中搜尋這些問題,並修正所發現的問題。
V8 遷移程序
如要將指令碼遷移至 V8,請按照下列步驟操作:
- 為指令碼啟用 V8 執行階段。
- 請詳閱下列不相容性說明。請檢查指令碼,確認是否存在任何不相容問題。如果存在一或多個不相容問題,請調整指令碼程式碼加以移除或避免問題。
- 請詳閱下列其他差異。 檢查指令碼,判斷這裡列出的有任何差異是否影響程式碼的行為。請調整指令碼來修正行為。
- 修正發現的不相容問題或其他差異後,您就可以開始更新程式碼,視需要使用 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 年之間二位數的年份,其他日期則傳回四位數的年份,其他日期則為 4 位數的年份,也就是 JavaScript 1.2 以下版本的行為。
在 V8 執行階段中,Date.prototype.getYear()
會改為根據 ECMAScript 標準的要求,改為傳回減 1900 的年份。
將指令碼遷移至 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__
函式,以便將自訂疊代器新增至任何子句。這也適用於 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); } |
避免條件式擷取子句
V8 執行階段不支援 catch..if
條件式擷取子句,因為這些子句與標準不符。
將指令碼遷移至 V8 時,請將主體主體中的所有擷取條件移至:
// 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
。這個堆疊也是非標準,但 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) |
調整字串化列舉物件的處理方式
在原始 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 在其中使用 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 在其中使用 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 執行階段執行的獨立指令碼,您必須至少為使用者提供該指令碼的檢視權限,指令碼的觸發條件才能正常運作。