将脚本迁移到 V8 运行时

如果您已有使用 Rhino 运行时的脚本,并且希望使用 V8 语法和功能,则必须将脚本迁移到 V8

使用 Rhino 运行时编写的大多数脚本都可以使用 V8 运行时运行 无需进行任何调整这通常是添加 V8 语法和 添加功能 启用 V8 运行时

然而,有一小部分 不兼容性 以及可能会导致生成脚本的其他差异 在启用 V8 运行时后出现故障或行为异常。在迁移过程中 脚本来使用 V8,则必须在脚本项目中搜索这些问题并 更正您找到的任何错误。

V8 迁移过程

要将脚本迁移到 V8,请按以下步骤操作:

  1. 启用 V8 运行时
  2. 仔细查看不兼容问题 。检查您的脚本,确定是否存在 存在不兼容的情况;如果存在一个或多个不兼容的情况 请调整您的脚本代码,移除或避免问题。
  3. 请仔细查看下方列出的其他差异。 检查您的脚本,确定列出的任何差异是否会影响 代码行为调整脚本以纠正行为。
  4. 更正了发现的任何不兼容问题或其他错误后, 您可以开始更新代码,以便使用 V8 语法和其他功能
  5. 完成代码调整后,请全面测试您的脚本, 确保它按预期运行
  6. 如果您的脚本是 Web 应用或已发布的插件, 您必须 创建新版本 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 运行时中,对于 1900 年至 1999 年,Date.prototype.getYear() 会返回两位数年份,但对于其他日期,则会返回四位数年份,这与 JavaScript 1.2 及更早版本中的行为相同。

在 V8 运行时中 Date.prototype.getYear() 而是根据 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 脚本项目直接使用 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 添加了一项功能,允许向任何 cla 添加自定义迭代器 方法是在该类的原型中声明一个 __iterator__ 函数;这是 还添加到了 Apps 脚本的 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 正文

// 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.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 脚本服务和 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 中,移除了隐式特殊上下文。脚本中定义的全局变量和函数会放置在全局上下文中,与内置的 Google Apps 脚本服务和 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 的构造函数作为 a instanceof b 的替代方案。用法: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'));
}

//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 运行时上运行的独立脚本,您需要至少向用户提供对脚本的查看权限,才能让脚本的触发器正常运行。