Memigrasikan skrip ke runtime V8

Jika Anda sudah memiliki skrip yang menggunakan runtime Rhino dan ingin menggunakan sintaksis dan fitur V8, Anda harus memigrasikan skrip ke V8.

Sebagian besar skrip yang ditulis menggunakan runtime Rhino dapat beroperasi menggunakan runtime V8 tanpa penyesuaian. Sering kali, satu-satunya prasyarat untuk menambahkan sintaksis dan fitur V8 ke skrip adalah mengaktifkan runtime V8.

Namun, ada sejumlah kecil inkompatibilitas dan perbedaan lainnya yang dapat mengakibatkan skrip gagal atau berperilaku secara tidak terduga setelah mengaktifkan runtime V8. Saat memigrasikan skrip untuk menggunakan V8, Anda harus menelusuri masalah ini dalam project skrip dan memperbaiki apa pun yang Anda temukan.

Prosedur migrasi V8

Untuk memigrasikan skrip ke V8, ikuti prosedur ini:

  1. Aktifkan runtime V8 untuk skrip.
  2. Tinjau dengan cermat inkompatibilitas yang tercantum di bawah. Periksa skrip Anda untuk menentukan apakah ada inkompatibilitas yang ada; jika ada satu atau beberapa inkompatibilitas, sesuaikan kode skrip untuk menghapus atau menghindari masalah.
  3. Tinjau perbedaan lain yang tercantum di bawah ini dengan cermat. Periksa skrip Anda untuk mengetahui apakah salah satu perbedaan yang tercantum memengaruhi perilaku kode. Sesuaikan skrip Anda untuk memperbaiki perilaku.
  4. Setelah memperbaiki inkompatibilitas yang ditemukan atau perbedaan lainnya, Anda dapat mulai memperbarui kode untuk menggunakan sintaksis V8 dan fitur lainnya sesuai keinginan.
  5. Setelah menyelesaikan penyesuaian kode, uji skrip Anda secara menyeluruh untuk memastikan perilakunya seperti yang diharapkan.
  6. Jika skrip Anda adalah aplikasi web atau add-on yang dipublikasikan, Anda harus membuat versi baru skrip dengan penyesuaian V8. Agar versi V8 tersedia bagi pengguna, Anda harus memublikasikan ulang skrip dengan versi ini.

Inkompatibilitas

Sayangnya, runtime Apps Script berbasis Rhino yang asli mengizinkan beberapa perilaku ECMAScript non-standar. Karena V8 telah mematuhi standar, perilaku ini tidak didukung setelah migrasi. Kegagalan memperbaiki masalah ini akan menyebabkan error atau perilaku skrip rusak setelah runtime V8 diaktifkan.

Bagian berikut menjelaskan setiap perilaku dan langkah-langkah ini yang harus Anda lakukan untuk memperbaiki kode skrip selama migrasi ke V8.

Menghindarinfor each(variable in object)

Pernyataan for each (variable in object) ditambahkan ke JavaScript 1.6, dan dihapus dan diganti dengan for...of.

Saat memigrasikan skrip Anda ke V8, hindari penggunaan pernyataan for each (variable in object).

Sebagai gantinya, gunakan 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);
}
      

MenghindarinDate.prototype.getYear()

Dalam runtime Rhino yang asli, Date.prototype.getYear() menampilkan tahun dua digit untuk tahun dari 1900-1999, sedangkan tahun empat digit untuk tanggal lainnya, yang merupakan perilaku di JavaScript 1.2 dan yang lebih lama.

Dalam runtime V8, Date.prototype.getYear() menampilkan tahun dikurangi 1900, seperti yang diwajibkan oleh standar ECMAScript.

Saat memigrasikan skrip Anda ke V8, selalu gunakan Date.prototype.getFullYear(), yang menampilkan tahun empat digit, terlepas dari tanggalnya.

Hindari penggunaan kata kunci yang dicadangkan sebagai nama

ECMAScript melarang penggunaan kata kunci cadangan tertentu dalam nama fungsi dan variabel. Runtime Rhino memungkinkan banyak kata ini, jadi jika kode Anda menggunakannya, Anda harus mengganti nama fungsi atau variabel.

Saat memigrasikan skrip Anda ke V8, jangan menamai variabel atau fungsi menggunakan salah satu kata kunci yang dicadangkan. Ganti nama variabel atau fungsi agar tidak menggunakan nama kata kunci. Penggunaan umum kata kunci sebagai nama adalah class, import, dan export.

Hindari menetapkan ulang variabel const

Pada runtime Rhino yang asli, Anda dapat mendeklarasikan variabel menggunakan const yang berarti nilai simbol tidak pernah berubah dan penugasan berikutnya ke simbol akan diabaikan.

Pada runtime V8 yang baru, kata kunci const sudah mematuhi standar dan menetapkan ke variabel yang dideklarasikan sebagai const akan menyebabkan error runtime TypeError: Assignment to constant variable.

Saat memigrasikan skrip Anda ke V8, jangan mencoba menetapkan ulang nilai variabel 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
      

Menghindari literal XML dan objek XML

Ekstensi non-standar ke ECMAScript ini memungkinkan project Apps Script untuk menggunakan sintaksis XML secara langsung.

Saat memigrasikan skrip Anda ke V8, hindari penggunaan literal XML langsung atau objek XML.

Sebagai gantinya, gunakan XmlService untuk mengurai 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
      

Jangan membuat fungsi iterator kustom menggunakan __iterator__

JavaScript 1.7 menambahkan fitur yang memungkinkan penambahan iterator kustom ke setiap klausa dengan mendeklarasikan fungsi __iterator__ dalam prototipe class tersebut. Fungsi ini juga ditambahkan ke runtime Rhino Apps Script untuk memudahkan developer. Namun, fitur ini tidak pernah menjadi bagian dari standar ECMA-262 dan telah dihapus di mesin JavaScript yang sesuai dengan ECMAScript. Skrip yang menggunakan V8 tidak dapat menggunakan konstruksi iterator ini.

Saat memigrasikan skrip Anda ke V8, hindari fungsi __iterator__ untuk membuat iterator kustom. Sebagai gantinya, gunakan iterator ECMAScript 6.

Pertimbangkan konstruksi array berikut:

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

Contoh kode berikut menunjukkan cara pembuatan iterator dalam runtime Rhino, dan cara membuat iterator pengganti dalam runtime 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);
}
      

Menghindari klausa catch bersyarat

Runtime V8 tidak mendukung klausa tangkapan bersyarat catch..if, karena tidak mematuhi standar.

Saat memigrasikan skrip Anda ke V8, pindahkan kondisional catch ke dalam isi 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
  }
}

Hindari penggunaan Object.prototype.toSource()

JavaScript 1.3 berisi metode Object.prototype.toSource() yang tidak pernah menjadi bagian dari standar ECMAScript. API ini tidak didukung dalam runtime V8.

Saat memigrasikan skrip Anda ke V8, hapus semua penggunaan Object.prototype.toSource() dari kode Anda.

Perbedaan lainnya

Selain inkompatibilitas di atas yang dapat menyebabkan kegagalan skrip, ada beberapa perbedaan lain yang, jika tidak dikoreksi, dapat mengakibatkan perilaku skrip runtime V8 yang tidak terduga.

Bagian berikut menjelaskan cara mengupdate kode skrip untuk menghindari kejutan yang tidak terduga ini.

Menyesuaikan format tanggal dan waktu khusus lokalitas

Metode Date toLocaleString(), toLocaleDateString(), dan toLocaleTimeString() berperilaku berbeda dalam runtime V8 jika dibandingkan dengan Rhino.

Di Rhino, format defaultnya adalah format panjang, dan parameter apa pun yang diteruskan akan diabaikan.

Pada runtime V8, format default-nya adalah format pendek dan parameter yang diteruskan ditangani sesuai dengan standar ECMA (lihat dokumentasi toLocaleDateString() untuk detailnya).

Saat memigrasikan skrip ke V8, uji dan sesuaikan ekspektasi kode Anda terkait output metode tanggal dan waktu khusus lokalitas:

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

Hindari penggunaan Error.fileName dan Error.lineNumber

Dalam untime V8, objek Error JavaScript standar tidak mendukung fileName atau lineNumber sebagai parameter konstruktor atau properti objek.

Saat memigrasikan skrip Anda ke V8, hapus ketergantungan pada Error.fileName dan Error.lineNumber.

Alternatifnya adalah menggunakan Error.prototype.stack. Stack ini juga non-standar, tetapi didukung di Rhino dan V8. Format pelacakan tumpukan yang dihasilkan oleh kedua platform sedikit berbeda:

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

Menyesuaikan penanganan objek enum yang di-string

Dalam runtime Rhino yang asli, penggunaan metode JSON.stringify() JavaScript pada objek enum hanya akan menampilkan {}.

Di V8, menggunakan metode yang sama pada objek enum akan mengganti nama enum.

Saat memigrasikan skrip Anda ke V8, uji dan sesuaikan ekspektasi kode Anda terkait output JSON.stringify() pada objek 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"

Menyesuaikan penanganan parameter yang tidak ditentukan

Dalam runtime Rhino asli, meneruskan undefined ke metode sebagai parameter akan meneruskan string "undefined" ke metode tersebut.

Di V8, meneruskan undefined ke metode sama dengan meneruskan null.

Saat memigrasikan skrip Anda ke V8, uji dan sesuaikan ekspektasi kode terkait parameter 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.

Menyesuaikan penanganan this global

Runtime Rhino mendefinisikan konteks khusus implisit untuk skrip yang menggunakannya. Kode skrip berjalan dalam konteks implisit ini, berbeda dengan this global yang sebenarnya. Ini berarti referensi ke "this global" dalam kode benar-benar mengevaluasi ke konteks khusus, yang hanya berisi kode dan variabel yang ditentukan dalam skrip. Layanan Apps Script dan objek ECMAScript bawaan dikecualikan dari penggunaan this ini. Situasi ini mirip dengan struktur JavaScript ini:

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

Di V8, konteks khusus implisit dihapus. Fungsi dan variabel global yang ditentukan dalam skrip ditempatkan dalam konteks global, di samping layanan Apps Script bawaan dan bawaan ECMAScript seperti Math dan Date.

Saat memigrasikan skrip Anda ke V8, uji dan sesuaikan ekspektasi kode Anda terkait penggunaan this dalam konteks global. Pada umumnya, perbedaannya hanya akan terlihat jika kode Anda memeriksa kunci atau nama properti objek this global:

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

Menyesuaikan penanganan instanceof di library

Penggunaan instanceof dalam library pada objek yang diteruskan sebagai parameter dalam fungsi dari project lain dapat memberikan negatif palsu. Dalam runtime V8, project dan library-nya dijalankan dalam konteks eksekusi yang berbeda sehingga memiliki rantai prototipe dan global yang berbeda.

Perhatikan bahwa hal ini hanya berlaku jika library Anda menggunakan instanceof pada objek yang tidak dibuat dalam project Anda. Menggunakannya pada objek yang dibuat dalam project Anda, baik dalam skrip yang sama atau berbeda dalam project Anda, akan berfungsi seperti yang diharapkan.

Jika project yang berjalan di V8 menggunakan skrip Anda sebagai library, periksa apakah skrip Anda menggunakan instanceof pada parameter yang akan diteruskan dari project lain. Sesuaikan penggunaan instanceof dan gunakan alternatif lain yang memungkinkan sesuai kasus penggunaan Anda.

Salah satu alternatif untuk a instanceof b adalah menggunakan konstruktor a jika Anda tidak perlu menelusuri seluruh rantai prototipe dan cukup memeriksa konstruktor tersebut. Penggunaan: a.constructor.name == "b"

Pertimbangkan Proyek A dan Proyek B di mana Proyek A menggunakan Proyek B sebagai perpustakaan.

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

Alternatif lain dapat berupa dengan memperkenalkan fungsi yang memeriksa instanceof dalam project utama dan meneruskan fungsi selain parameter lain saat memanggil fungsi library. Fungsi yang diteruskan kemudian dapat digunakan untuk memeriksa instanceof di dalam library.

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

Menyesuaikan penerusan resource yang tidak dibagikan ke library

Meneruskan resource yang tidak dibagikan dari skrip utama ke library berfungsi secara berbeda dalam runtime V8.

Dalam runtime Rhino, meneruskan resource non-bersama tidak akan berfungsi. Sebagai gantinya, library menggunakan resource-nya sendiri.

Dalam runtime V8, meneruskan resource yang tidak dibagikan ke library akan berfungsi. Library menggunakan resource non-bersama yang diteruskan.

Jangan meneruskan resource yang tidak dibagikan sebagai parameter fungsi. Selalu deklarasikan resource yang tidak dibagikan dalam skrip yang sama yang menggunakannya.

Pertimbangkan Proyek A dan Proyek B di mana Proyek A menggunakan Proyek B sebagai perpustakaan. Dalam contoh ini, PropertiesService adalah resource non-bersama.

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

Perbarui akses ke skrip mandiri

Untuk skrip mandiri yang berjalan di runtime V8, Anda harus memberi pengguna setidaknya akses lihat ke skrip agar pemicu skrip dapat berfungsi dengan baik.