Môi trường thời gian chạy Rhino sẽ ngừng hoạt động sớm nhất từ ngày 31 tháng 1 năm 2026. Nếu có một tập lệnh hiện tại đang sử dụng thời gian chạy Rhino, bạn phải di chuyển tập lệnh đó sang V8.
Thông thường, điều kiện tiên quyết duy nhất để thêm cú pháp và các tính năng V8 vào một tập lệnh là bật môi trường thời gian chạy V8. Tuy nhiên, có một số điểm không tương thích và những điểm khác biệt khác có thể khiến một tập lệnh không thành công hoặc hoạt động không như mong đợi trong 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 những vấn đề này trong dự án tập lệnh và sửa mọi vấn đề mà 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 này:
- Bật thời gian chạy V8 cho tập lệnh. Bạn có thể kiểm tra
runtimeVersion
bằng cách sử dụng manifest cho dự án Apps Script. - Hãy xem xét kỹ những điểm không tương thích sau đây. Kiểm tra tập lệnh để xác định xem có sự không tương thích nào hay không; nếu có một hoặc nhiều sự không tương thích, hãy điều chỉnh mã tập lệnh để xoá hoặc tránh vấn đề này.
- Hãy xem xét kỹ những điểm khác biệt khác sau đây. Kiểm tra tập lệnh để xác định xem có điểm khác biệt nào trong danh sách ảnh hưởng đến hành vi của mã hay không. Điều chỉnh tập lệnh để khắc phục hành vi này.
- Sau khi khắc phục mọi điểm 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.
- Sau khi hoàn tất việc điều chỉnh mã, hãy kiểm tra kỹ tập lệnh để đảm bảo tập lệnh hoạt động như mong đợi.
- Nếu tập lệnh của bạn là một ứng dụng web hoặc tiện ích bổ sung đã xuất bản, bạn phải tạo một phiên bản mới của tập lệnh có các điều chỉnh V8 và trỏ việc triển khai đến phiên bản mới tạo. Để cung cấp phiên bản V8 cho người dùng, bạn phải xuất bản lại tập lệnh bằng phiên bản này.
- Nếu tập lệnh của bạn được dùng làm thư viện, hãy tạo một phiên bản triển khai mới cho tập lệnh đó. Thông báo phiên bản mới này cho tất cả tập lệnh và người dùng sử dụng thư viện của bạn, hướng dẫn họ cập nhật lên phiên bản có hỗ trợ V8. Xác minh rằng mọi phiên bản cũ hơn dựa trên Rhino của thư viện đều không còn được sử dụng hoặc truy cập được nữa.
- Xác minh rằng không có phiên bản nào của tập lệnh vẫn đang hoạt động trên thời gian chạy Rhino cũ. Xác minh rằng tất cả các lượt triển khai đều được liên kết với một phiên bản trên V8. Lưu trữ các bản triển khai cũ. Xem xét tất cả các phiên bản và xoá những phiên bản không sử dụng V8 Runtime.
Tính không tương thích
Rất tiếc là thời gian chạy Apps Script dựa trên Rhino ban đầu đã cho phép một số hành vi ECMAScript không chuẩn. Vì V8 tuân thủ các tiêu chuẩn, nên những hành vi này không được hỗ trợ sau khi di chuyển. Nếu không khắc phục những vấn đề này, bạn sẽ gặp lỗi hoặc tập lệnh hoạt động không đúng cách 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 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á để thay bằng for...of
.
Khi di chuyển tập lệnh sang V8, hãy tránh sử dụng các 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 có hai chữ số cho các năm từ 1900 đến 1999, nhưng trả về năm có bốn chữ số cho các ngày khác. Đây 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ừ đi 1900 theo yêu cầu của 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()
. Hàm này sẽ trả về năm có 4 chữ số, bất kể ngày nào.
Tránh sử dụng 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 trong tên hàm và tên biến. Thời gian chạy Rhino cho phép nhiều từ trong số này, vì vậy, nếu mã của bạn sử dụng các từ này, bạn phải đổi tên hàm hoặc biến.
Khi di chuyển tập lệnh sang V8, hãy tránh đặt tên biến hoặc hàm bằng một trong các từ khoá dành riêng.
Đổi tên mọi biến hoặc hàm để tránh sử dụng tên từ khoá. Các cách sử dụng phổ biến của từ khoá dưới dạng tên là class
, import
và export
.
Tránh gán 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 cách sử dụng const
, tức là giá trị của biểu tượng không bao giờ thay đổi và các lần 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à việc chỉ định cho một biến được khai báo dưới dạng 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 cố gắng chỉ định lại giá trị của một 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 dùng chữ cố định XML và đối tượng XML
Tiện ích không chuẩn này cho ECMAScript cho phép các dự án Apps Script sử dụng trực tiếp cú pháp XML.
Khi di chuyển tập lệnh sang V8, hãy 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 các 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 trình lặp tuỳ chỉnh vào bất kỳ lớp nào bằng cách khai báo một 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 để mang lại sự thuận tiện cho nhà phát triển. Tuy nhiên, tính năng này chưa bao giờ thuộc 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 trình lặp này.
Khi di chuyển tập lệnh sang V8, hãy tránh hàm __iterator__
để tạo trình lặp tuỳ chỉnh. Thay vào đó, hãy dùng trình lặp lại ECMAScript 6.
Hãy xem xét cấu trúc 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 ví dụ về mã sau đây cho thấy cách tạo một trình lặp trong thời gian chạy Rhino và cách tạo một trình 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
Thời gian chạy V8 không hỗ trợ các mệnh đề bắt có điều kiện catch..if
vì chúng 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 lỗi bên trong phần nội dung bắt lỗi:
// 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 có một phương thức Object.prototype.toSource() chưa bao giờ thuộc bất kỳ tiêu chuẩn ECMAScript nào. 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 cách sử dụng Object.prototype.toSource() khỏi mã của bạn.
Các đ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òn có một số điểm khác biệt mà nếu không được khắc phục, có thể dẫn đến hành vi tập lệnh thời gian chạy V8 không mong muốn.
Các phần sau đây giải thích cách cập nhật mã tập lệnh để tránh những điều bất ngờ này.
Điều chỉnh định dạng ngày và giờ theo ngôn ngữ
Các phương thức Date
, toLocaleString()
, toLocaleDateString()
và 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 là định dạng dài và mọi tham số được truyền vào đều 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ố được truyền vào sẽ được xử lý theo tiêu chuẩn ECMA (xem tài liệu 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 các kỳ vọng của mã về đầu ra của các phương thức ngày và giờ dành riêng cho từng ngôn ngữ:
// 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.fileName
và Error.lineNumber
Trong thời gian chạy V8, đối tượng JavaScript tiêu chuẩn Error
không hỗ trợ fileName
hoặc lineNumber
làm tham số của 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 xoá mọi sự phụ thuộc vào Error.fileName
và Error.lineNumber
.
Một lựa chọn khác là sử dụng Error.prototype.stack
.
Ngăn xếp này cũng không phải là ngăn xếp tiêu chuẩn, nhưng được hỗ trợ trong V8. Định dạng của dấu vết ngăn xếp do hai nền tảng này tạo ra có chút khác biệt:
// 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 được chuyển đổi thành chuỗi
Trong thời gian chạy Rhino ban đầu, việc sử dụng phương thức JavaScript JSON.stringify()
trên một đối tượng enum chỉ trả về {}
.
Trong V8, việc sử dụng cùng một phương thức trên một đối tượng enum sẽ trả về tên enum.
Khi di chuyển tập lệnh sang V8, hãy kiểm thử và điều chỉnh các kỳ vọng của mã về đầu ra của JSON.stringify()
trên các đố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 cách 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
vào một phương thức dưới dạng tham số sẽ dẫn đến việc truyền chuỗi "undefined"
vào phương thức đó.
Trong V8, việc truyền undefined
đến 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 các kỳ vọng của mã về undefined
tham số:
// 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 cách xử lý this
trên toàn cầu
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 thời gian chạy này.
Mã tập lệnh chạy trong ngữ cảnh ngầm này, khác với this
thực tế trên toàn cầu. Điều này có nghĩa là các tham chiếu đến "this
toàn cầu" trong mã thực sự đánh giá theo ngữ cảnh đặc biệt, 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 sẵn và các đối tượng ECMAScript sẽ không được dùng this
. 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 chung được xác định trong tập lệnh sẽ được đặt trong ngữ cảnh chung, bên cạnh các dịch vụ Apps Script tích hợp và các thành phần tích hợp ECMAScript như Math
và Date
.
Khi di chuyển tập lệnh sang V8, hãy kiểm thử và điều chỉnh các kỳ vọng của mã về việc sử dụng this
trong bối cảnh chung. Trong hầu hết 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 cách xử lý instanceof
trong các thư viện
Việc sử dụng instanceof
trong một thư viện trên một đối tượng được truyền dưới dạng tham số trong một hàm từ một dự án khác có thể cho 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 đó sẽ chạy trong các bối cảnh thực thi khác nhau, do đó có các chuỗi nguyên mẫu và biến toàn cục khác nhau.
Xin lưu ý rằng điều 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 nó trên một đố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 một tập lệnh khác trong dự án của bạn, đều sẽ hoạt động như mong đợi.
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. Điều chỉnh việc sử dụng instanceof
và sử dụng các lựa chọn thay thế khả thi khác theo trường hợp sử dụng của bạn.
Một lựa chọn thay thế cho a instanceof b
là sử dụng hàm khởi tạo của a
trong trường hợp bạn không cần tìm kiếm toàn bộ chuỗi nguyên mẫu và 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 giải pháp thay thế 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 ngoài các tham số khác khi gọi một hàm thư viện. Sau đó, bạn có thể dùng hàm đã truyền để 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 dùng chung đến thư viện
Việc truyền tài nguyên không dùng chung từ tập lệnh chính đến một thư viện hoạt động 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 dùng chung sẽ không hoạt động. Thay vào đó, thư viện sẽ 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 dùng chung vào thư viện sẽ hoạt động. Thư viện sử dụng tài nguyên không dùng chung đã truyền.
Không truyền các tài nguyên không dùng chung làm tham số hàm. Luôn khai báo các tài nguyên không dùng chung 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 dùng chung.
// 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')); } |
Cập nhật quyền truy cập vào 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, bạn cần cấp cho người dùng ít nhất quyền xem đối với tập lệnh để các trình kích hoạt của tập lệnh hoạt động đúng cách.