Di chuyển tập lệnh sang thời gian chạy V8

Nếu có sẵn một tập lệnh bằng môi trường thời gian chạy Rhino và muốn sử dụng cú pháp cũng như các tính năng của V8, bạn phải di chuyển tập lệnh sang V8.

Hầu hết các tập lệnh được viết bằng thời gian chạy Rhino đều có thể hoạt động bằng thời gian chạy V8 mà không cần điều chỉnh. Thông thường, điều kiện tiên quyết duy nhất để thêm cú pháp và tính năng V8 vào tập lệnh là bật thời gian chạy V8.

Tuy nhiên, có một nhóm nhỏ không tương thíchđiểm khác biệt khác có thể khiến tập lệnh không thành công hoặc hoạt động không như mong đợi sau khi bật thời gian chạy V8. Khi di chuyển một tập lệnh để sử dụng V8, bạn phải tìm kiếm các vấn đề này trong dự án tập lệnh và sửa mọi vấn đề bạn tìm thấy.

Quy trình di chuyển V8

Để di chuyển một tập lệnh sang V8, hãy làm theo quy trình sau:

  1. Bật thời gian chạy V8 cho tập lệnh.
  2. Hãy xem xét kỹ các không tương thích được liệt kê bên dưới. Kiểm tra tập lệnh của bạn để xác định xem có điểm không tương thích nào không; nếu có một hoặc nhiều điểm không tương thích, hãy điều chỉnh đoạn mã của tập lệnh để loại bỏ hoặc tránh vấn đề.
  3. Hãy xem kỹ các điểm khác biệt khác trong danh sách dưới đây. Kiểm tra tập lệnh của bạn để xác định xem có bất kỳ điểm khác biệt nào được liệt kê ở trên có ảnh hưởng đến hành vi của mã hay không. Hãy điều chỉnh tập lệnh của bạn để khắc phục hành vi.
  4. Sau khi khắc phục mọi vấn đề không tương thích hoặc các điểm khác biệt khác đã phát hiện, bạn có thể bắt đầu cập nhật mã để sử dụng cú pháp V8 và các tính năng khác nếu muốn.
  5. Sau khi hoàn tất việc điều chỉnh mã, hãy kiểm thử kỹ tập lệnh của bạn để đảm bảo tập lệnh hoạt động như dự kiến.
  6. Nếu tập lệnh của bạn là một ứng dụng web hoặc đã phát hành tiện ích bổ sung, bạn phải tạo phiên bản mới của tập lệnh với các điều chỉnh V8. Để người dùng có thể sử dụng phiên bản V8, bạn phải phát hành lại tập lệnh bằng phiên bản này.

Không tương thích

Rất tiếc, môi trường thời gian chạy Apps Script ban đầu dựa trên Rhino đã cho phép một số hành vi ECMAScript không chuẩn. Vì V8 tuân thủ tiêu chuẩn nên các hành vi này sẽ không được hỗ trợ sau khi di chuyển. Việc không khắc phục những vấn đề này sẽ dẫn đến lỗi hoặc hành vi của tập lệnh bị hỏng sau khi bật môi trường thời gian chạy V8.

Các phần sau đây mô tả từng hành vi trong số này và các bước bạn phải thực hiện để sửa mã tập lệnh trong quá trình di chuyển sang V8.

Tránh for each(variable in object)

Câu lệnh for each (variable in object) đã được thêm vào JavaScript 1.6 và bị xoá và thay vào đó là for...of.

Khi di chuyển tập lệnh sang V8, hãy tránh sử dụng câu lệnh for each (variable in object).

Thay vào đó, hãy sử dụng 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);
}
      

Tránh Date.prototype.getYear()

Trong thời gian chạy Rhino ban đầu, Date.prototype.getYear() trả về năm gồm hai chữ số cho các năm từ 1900 đến 1999, nhưng trả về năm gồm 4 chữ số cho các ngày khác (là hành vi trong JavaScript 1.2 trở về trước).

Trong thời gian chạy V8, Date.prototype.getYear() sẽ trả về năm trừ 1900 theo yêu cầu của các tiêu chuẩn ECMAScript.

Khi di chuyển tập lệnh sang V8, hãy luôn sử dụng Date.prototype.getFullYear(). Mã này sẽ trả về năm gồm bốn chữ số bất kể ngày tháng.

Tránh sử dụng các từ khoá dành riêng làm tên

ECMAScript cấm sử dụng một số từ khoá dành riêng nhất định trong tên hàm và tên biến. Môi trường thời gian chạy Rhino cho phép nhiều từ này trong số này, vì vậy, nếu mã của bạn sử dụng những từ đó, bạn phải đổi tên các hàm hoặc biến của mình.

Khi di chuyển tập lệnh sang V8, hãy tránh đặt tên cho biến hoặc hàm bằng một trong các từ khoá dành riêng. Đổi tên bất kỳ biến hoặc hàm nào để tránh sử dụng tên từ khoá. Các cách phổ biến của các từ khoá làm tên là class, importexport.

Tránh chỉ định lại các biến const

Trong thời gian chạy Rhino ban đầu, bạn có thể khai báo một biến bằng const. Điều này có nghĩa là giá trị của biểu tượng sẽ không bao giờ thay đổi và các chỉ định trong tương lai cho biểu tượng đó sẽ bị bỏ qua.

Trong thời gian chạy V8 mới, từ khoá const tuân thủ tiêu chuẩn và gán cho một biến được khai báo là const sẽ dẫn đến lỗi thời gian chạy TypeError: Assignment to constant variable.

Khi di chuyển tập lệnh sang V8, đừng tìm cách chỉ định lại giá trị của biến 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
      

Tránh sử dụng các giá trị cố định XML và đối tượng XML

Tiện ích không chuẩn này của ECMAScript cho phép các dự án Apps Script trực tiếp sử dụng cú pháp XML.

Khi di chuyển tập lệnh sang V8, bạn nên tránh sử dụng các giá trị cố định XML trực tiếp hoặc đối tượng XML.

Thay vào đó, hãy sử dụng XmlService để phân tích cú pháp 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
      

Không tạo hàm lặp tuỳ chỉnh bằng __iterator__

JavaScript 1.7 thêm một tính năng cho phép thêm một biến lặp tuỳ chỉnh vào mọi clas bằng cách khai báo hàm __iterator__ trong nguyên mẫu của lớp đó; tính năng này cũng được thêm vào thời gian chạy Rhino của Apps Script để thuận tiện cho nhà phát triển. Tuy nhiên, tính năng này chưa từng nằm trong tiêu chuẩn ECMA-262 và đã bị xoá trong các công cụ JavaScript tuân thủ ECMAScript. Các tập lệnh sử dụng V8 không thể dùng cấu trúc biến lặp này.

Khi di chuyển tập lệnh sang V8, hãy tránh dùng hàm __iterator__ để tạo các biến lặp tuỳ chỉnh. Thay vào đó, hãy sử dụng trình lặp ECMAScript 6.

Hãy cân nhắc việc tạo mảng sau:

// 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.
      

Các mã ví dụ sau đây cho thấy cách tạo một biến lặp trong thời gian chạy Rhino và cách tạo một biến lặp thay thế trong thời gian chạy 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);
}
      

Tránh các mệnh đề bắt có điều kiện

Môi trường thời gian chạy V8 không hỗ trợ các mệnh đề phát hiện có điều kiện catch..if vì các mệnh đề này không tuân thủ tiêu chuẩn.

Khi di chuyển tập lệnh sang V8, hãy di chuyển mọi điều kiện bắt bóng bên trong nội dung lời nhắc:

// 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
  }
}

Tránh sử dụng Object.prototype.toSource()

JavaScript 1.3 chứa phương thức Object.prototype.toSource() chưa từng nằm trong tiêu chuẩn ECMAScript. Tính năng này không được hỗ trợ trong thời gian chạy V8.

Khi di chuyển tập lệnh sang V8, hãy xoá mọi trường hợp sử dụng Object.prototype.toSource() khỏi mã của bạn.

Điểm khác biệt khác

Ngoài những điểm không tương thích nêu trên có thể gây ra lỗi tập lệnh, có một số điểm khác biệt khác mà nếu không được khắc phục, có thể dẫn đến hành vi không mong muốn của tập lệnh trong thời gian chạy V8.

Các phần sau giải thích cách cập nhật mã tập lệnh của bạn để tránh những tình huống bất ngờ không mong muốn này.

Điều chỉnh định dạng ngày và giờ theo ngôn ngữ cụ thể

Các phương thức Date toLocaleString(), toLocaleDateString()toLocaleTimeString() hoạt động khác nhau trong thời gian chạy V8 so với Rhino.

Trong Rhino, định dạng mặc định sẽ là định dạng dài và mọi tham số được truyền vào sẽ bị bỏ qua.

Trong thời gian chạy V8, định dạng mặc định là định dạng ngắn và các tham số đã truyền vào được xử lý theo tiêu chuẩn ECMA (xem tài liệu về toLocaleDateString() để biết thông tin chi tiết).

Khi di chuyển tập lệnh sang V8, hãy kiểm thử và điều chỉnh kỳ vọng của mã liên quan đến kết quả của phương thức ngày và giờ theo từng ngôn ngữ cụ thể:

// 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' }));
      

Tránh sử dụng Error.fileNameError.lineNumber

Trong thời gian chờ của V8, đối tượng JavaScript chuẩn Error không hỗ trợ fileName hoặc lineNumber dưới dạng tham số hàm khởi tạo hoặc thuộc tính đối tượng.

Khi di chuyển tập lệnh sang V8, hãy loại bỏ mọi sự phụ thuộc vào Error.fileNameError.lineNumber.

Bạn có thể sử dụng Error.prototype.stack. Ngăn xếp này cũng không theo chuẩn, nhưng được hỗ trợ trong cả Rhino và V8. Định dạng của dấu vết ngăn xếp do hai nền tảng tạo ra hơi khác nhau:

// 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)
      

Điều chỉnh cách xử lý các đối tượng enum dạng chuỗi

Trong thời gian chạy Rhino ban đầu, việc sử dụng phương thức JSON.stringify() JavaScript trên đối tượng enum chỉ trả về {}.

Trong V8, việc sử dụng cùng một phương thức trên đối tượng enum sẽ bật lại tên enum.

Khi di chuyển tập lệnh sang V8, hãy kiểm thử và điều chỉnh kỳ vọng của mã liên quan đến kết quả của JSON.stringify() trên đối tượng enum:

// 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"

Điều chỉnh việc xử lý các tham số không xác định

Trong thời gian chạy Rhino ban đầu, việc truyền undefined đến một phương thức dưới dạng tham số dẫn đến việc truyền chuỗi "undefined" vào phương thức đó.

Trong V8, việc truyền undefined vào các phương thức tương đương với việc truyền null.

Khi di chuyển tập lệnh sang V8, hãy kiểm thử và điều chỉnh kỳ vọng của mã liên quan đến các tham số 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.

Điều chỉnh quá trình xử lý this chung

Môi trường thời gian chạy Rhino xác định một ngữ cảnh đặc biệt ngầm ẩn cho các tập lệnh sử dụng bối cảnh đó. Mã tập lệnh chạy trong ngữ cảnh ngầm ẩn này, khác với this chung thực tế. Tức là các tham chiếu đến "this chung" trong mã sẽ thực sự đánh giá theo ngữ cảnh đặc biệt, trong đó chỉ chứa mã và các biến được xác định trong tập lệnh. Các dịch vụ Apps Script tích hợp và đối tượng ECMAScript sẽ không được dùng trong việc sử dụng this này. Tình huống này tương tự như cấu trúc JavaScript sau:

// 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.
}();

Trong V8, ngữ cảnh đặc biệt ngầm ẩn sẽ bị xoá. Các biến và hàm toàn cục được xác định trong tập lệnh được đặt trong ngữ cảnh chung, bên cạnh dịch vụ Apps Script và ECMAScript tích hợp sẵn như MathDate.

Khi di chuyển tập lệnh sang V8, hãy kiểm thử và điều chỉnh kỳ vọng của mã liên quan đến việc sử dụng this trong ngữ cảnh chung. Trong hầu hết các trường hợp, sự khác biệt chỉ rõ ràng nếu mã của bạn kiểm tra các khoá hoặc tên thuộc tính của đối tượng this chung:

// 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));
}

Điều chỉnh quá trình xử lý instanceof trong thư viện

Việc sử dụng instanceof trong thư viện trên một đối tượng được truyền dưới dạng tham số trong hàm từ một dự án khác có thể đưa ra kết quả âm tính giả. Trong thời gian chạy V8, một dự án và các thư viện của dự án đó được chạy trong nhiều ngữ cảnh thực thi nên có các chuỗi toàn cục và chuỗi nguyên mẫu khác nhau.

Xin lưu ý rằng trường hợp này chỉ xảy ra nếu thư viện của bạn sử dụng instanceof trên một đối tượng không được tạo trong dự án. Việc sử dụng thuộc tính này trên đối tượng được tạo trong dự án của bạn, cho dù trong cùng một tập lệnh hay trong một tập lệnh khác trong dự án, sẽ mang lại hiệu quả như dự kiến.

Nếu một dự án đang chạy trên V8 sử dụng tập lệnh của bạn làm thư viện, hãy kiểm tra xem tập lệnh của bạn có sử dụng instanceof trên một tham số sẽ được truyền từ một dự án khác hay không. Hãy điều chỉnh cách sử dụng instanceof và sử dụng các giải pháp thay thế khả thi khác tuỳ theo trường hợp sử dụng của bạn.

Một giải pháp thay thế cho a instanceof b có thể là sử dụng hàm khởi tạo của a trong các trường hợp mà bạn không cần tìm kiếm toàn bộ chuỗi nguyên mẫu mà chỉ cần kiểm tra hàm khởi tạo. Cách sử dụng: a.constructor.name == "b"

Hãy xem xét Dự án A và Dự án B, trong đó Dự án A sử dụng Dự án B làm thư viện.

//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
}

Một cách khác là giới thiệu một hàm kiểm tra instanceof trong dự án chính và truyền hàm này cùng với các tham số khác khi gọi hàm thư viện. Sau đó, hàm đã truyền có thể được dùng để kiểm tra instanceof bên trong thư viện.

//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);
}
      

Điều chỉnh việc truyền các tài nguyên không được chia sẻ vào thư viện

Việc chuyển tài nguyên không được chia sẻ từ tập lệnh chính đến một thư viện sẽ hoạt động theo cách khác trong thời gian chạy V8.

Trong thời gian chạy Rhino, việc truyền một tài nguyên không được chia sẻ sẽ không hoạt động. Thay vào đó, thư viện sử dụng tài nguyên riêng.

Trong thời gian chạy V8, việc truyền một tài nguyên không được chia sẻ vào thư viện sẽ hoạt động. Thư viện sử dụng tài nguyên không được chia sẻ đã truyền.

Không truyền các tài nguyên không được chia sẻ dưới dạng tham số hàm. Luôn khai báo các tài nguyên không được chia sẻ trong cùng một tập lệnh sử dụng các tài nguyên đó.

Hãy xem xét Dự án A và Dự án B, trong đó Dự án A sử dụng Dự án B làm thư viện. Trong ví dụ này, PropertiesService là một tài nguyên không được chia sẻ.

// 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); }

Cập nhật quyền truy cập vào các tập lệnh độc lập

Đối với các tập lệnh độc lập chạy trên thời gian chạy V8, ít nhất bạn cần cung cấp cho người dùng quyền xem tập lệnh để các điều kiện kích hoạt của tập lệnh hoạt động đúng cách.