Rhino 執行階段將於 2026 年 1 月 31 日或之後停用。如果您現有的指令碼使用 Rhino 執行階段,則必須將指令碼遷移至 V8。
如要將 V8 語法和功能新增至指令碼,通常只需要啟用 V8 執行階段即可。不過,仍有少數不相容和其他差異,可能導致指令碼在 V8 執行階段中失敗或出現非預期行為。將指令碼遷移至 V8 時,您必須在指令碼專案中搜尋這些問題,並修正找到的任何問題。
V8 遷移程序
如要將指令碼遷移至 V8,請按照下列程序操作:
- 為指令碼啟用 V8 執行階段。您可以使用 Apps Script 專案的資訊清單檢查
runtimeVersion
。 - 請詳閱下列不相容性。 檢查指令碼,判斷是否有任何不相容問題;如有,請調整指令碼程式碼,移除或避免這些問題。
- 請詳閱以下其他差異。 檢查指令碼,判斷列出的差異是否會影響程式碼行為。調整指令碼,修正行為。
- 修正發現的不相容或其他差異後,即可開始更新程式碼,使用 V8 語法和其他功能。
- 完成程式碼調整後,請徹底測試指令碼,確保運作正常。
- 如果指令碼是網頁應用程式或已發布的外掛程式,您必須建立新版指令碼,並進行 V8 調整,然後將部署作業指向新建立的版本。如要讓使用者使用 V8 版本,請務必使用這個版本重新發布指令碼。
- 如果指令碼做為程式庫使用,請為指令碼建立新的版本化部署作業。將這個新版本告知所有使用程式庫的指令碼和使用者,並指示他們更新至啟用 V8 的版本。確認您不再使用或存取任何舊版 (以 Rhino 為基礎) 程式庫。
- 確認沒有任何指令碼執行個體仍在舊版 Rhino 執行階段運作。確認所有部署作業都與 V8 版本的應用程式建立關聯。封存舊版部署作業。查看所有版本,並刪除未使用 V8 執行階段的版本。
不相容性
很遺憾,以 Rhino 為基礎的原始 Apps Script 執行階段允許數種非標準 ECMAScript 行為。由於 V8 符合標準,因此遷移後不支援這些行為。如果未修正這些問題,啟用 V8 執行階段後,指令碼就會發生錯誤或無法正常運作。
以下各節將說明這些行為,以及在遷移至 V8 時,您必須採取哪些步驟來修正指令碼程式碼。
建議不要使用 for each(variable in object)
JavaScript 1.6 新增了 for each (variable in object)
陳述式,但後來已移除,並改用 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__
函式,為該類別新增自訂疊代器。為方便開發人員,這項功能也已新增至 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
條件式 catch 子句,因為這類子句不符合標準。
將指令碼遷移至 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 執行階段指令碼出現非預期行為。
下列各節說明如何更新指令碼程式碼,避免發生這些意外狀況。
調整特定語言代碼的日期和時間格式
與 Rhino 相比,V8 執行階段的 Date
方法 toLocaleString()
、
toLocaleDateString()
和 toLocaleTimeString()
行為有所不同。
在 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()
方法只會傳回 {}
。
在 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
的建構函式做為 a instanceof b
的替代方案。使用方式:a.constructor.name == "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 執行階段執行,您至少需要提供指令碼的檢視權限給使用者,指令碼的觸發條件才能正常運作。