將指令碼遷移至 V8 執行階段

如果您已有使用 Rhino 執行階段的指令碼,且想將指令碼用於 V8 語法和功能,您必須將指令碼遷移至 V8

大多數使用 Rhino 執行階段編寫的指令碼,都可以使用 V8 執行階段運作,無須調整。通常只有加入 V8 語法和 編寫指令碼功能 啟用 V8 執行階段

不過,還是有少數 不相容性 以及可能導致指令碼的其他差異 啟用 V8 執行階段後,應用程式無法正常運作或出現未預期的行為。在將指令碼遷移至 V8 時,您必須搜尋指令碼專案中的這些問題,並修正所找到的問題。

V8 遷移程序

如要將指令碼遷移至 V8,請按照下列步驟操作:

  1. 啟用 V8 執行階段 指令碼
  2. 詳閱不相容性 。請檢查您的指令碼,以判斷其中 具相容性;如果出現一或多個不相容問題 調整指令碼,藉此移除或避免這個問題。
  3. 請詳閱下列其他差異。 檢查您的指令碼,判斷是否有下列任何差異影響 程式碼行為請調整指令碼來修正行為。
  4. 修正發現的不相容問題或其他問題後 您就可以開始更新程式碼 V8 語法和其他功能
  5. 完成程式碼調整後,請徹底測試指令碼, 確保應用程式能正常運作
  6. 如果指令碼是網頁應用程式或已發布外掛程式, 您必須 建立新版本 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 之間年份的兩位數年份,在其他時則傳回四位數年份 日期,也就是 JavaScript 1.2 以下版本中的行為。

在 V8 執行階段中 Date.prototype.getYear()敬上 會傳回減去 1900 的年份, ECMAScript 標準。

將指令碼遷移至 V8 時,請一律使用 Date.prototype.getFullYear(), 會傳回四位數的年份,無論日期為何。

避免使用保留關鍵字做為名稱

ECMAScript 禁止 預訂關鍵字 擷取及排序資料Rhino 執行階段允許使用其中許多字詞 因此,如果您的程式碼有使用這些參數,您就必須重新命名函式或變數。

將指令碼遷移至 V8 時,請避免為變數或函式命名 方法是使用 預訂關鍵字。 請重新命名任何變數或函式,避免使用關鍵字名稱。常見用途 包括 classimportexport 的關鍵字。

避免重新指派 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 時,請將任何 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() 呼叫程式庫

其他差異

除了上述的不相容問題以外,會造成指令碼失敗 是一種其他差異 執行階段指令碼行為

以下各節將說明如何更新指令碼,以免發生這類情形 非預期情況

調整地區專用的日期和時間格式設定

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.fileNameError.lineNumber

在 V8 時限內,只有標準 JavaScript Error敬上 物件不支援 fileNamelineNumber 做為建構函式參數 或物件屬性

將指令碼遷移至 V8 時 移除任何依附 Error.fileNameError.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 內建功能,例如 MathDate

將指令碼遷移至 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"

請考慮 Project 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 執行階段中,將非共用資源傳遞至程式庫運作。程式庫會使用傳遞的非共用資源。

請勿將非共用資源做為函式參數傳遞。一律在使用資源的相同指令碼中宣告非共用資源。

請考慮 Project 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'));
}

//Project B function setScriptProperties() { PropertiesService.getScriptProperties() .setProperty('project', 'Project-B'); } function getScriptProperties( propertiesService, key) { return propertiesService.getScriptProperties() .getProperty(key); }

// V8 runtime
// Project A
function testPassingNonSharedProperties() {
  PropertiesService.getScriptProperties()
      .setProperty('project', 'Project-A');
  B.setScriptProperties();
  // Prints: Project-A
  Logger.log(B.getScriptProperties(
      PropertiesService, 'project'));
}

// Project B function setProperties() { PropertiesService.getScriptProperties() .setProperty('project', 'Project-B'); } function getScriptProperties( propertiesService, key) { return propertiesService.getScriptProperties() .getProperty(key); }

更新獨立指令碼的存取權

針對在 V8 執行階段執行的獨立指令碼,您必須至少為使用者提供指令碼的檢視權限,才能讓指令碼的觸發事件正常運作。