העברת סקריפטים לסביבת זמן הריצה של V8

סביבת זמן הריצה של Rhino תושבת ב-31 בינואר 2026 או לאחר מכן. אם יש לכם סקריפט קיים שמשתמש בסביבת זמן הריצה של Rhino, אתם צריכים להעביר את הסקריפט ל-V8.

לרוב, הדרישה המוקדמת היחידה להוספת תחביר ותכונות של V8 לסקריפט היא הפעלת סביבת זמן הריצה של V8. עם זאת, יש קבוצה קטנה של חוסר תאימות והבדלים אחרים שיכולים לגרום לסקריפט להיכשל או להתנהג באופן לא צפוי בסביבת זמן הריצה של V8. כשמעבירים סקריפט לשימוש ב-V8, צריך לחפש את הבעיות האלה בפרויקט הסקריפט ולתקן את כל הבעיות שמוצאים.

תהליך המיגרציה ל-V8

כדי להעביר סקריפט ל-V8, פועלים לפי השלבים הבאים:

  1. מפעילים את זמן הריצה של V8 לסקריפט. אפשר לבדוק את runtimeVersion באמצעות המאניפסט של פרויקט Apps Script.
  2. חשוב לעיין בקפידה בבעיות התאימות הבאות. צריך לבדוק את הסקריפט כדי לראות אם יש בו בעיות תאימות. אם יש בעיה אחת או יותר, צריך לשנות את קוד הסקריפט כדי לפתור אותה.
  3. חשוב לעיין בקפידה בההבדלים האחרים שמפורטים בהמשך. בודקים את הסקריפט כדי לראות אם אחד מההבדלים שצוינו משפיע על התנהגות הקוד. משנים את הסקריפט כדי לתקן את ההתנהגות.
  4. אחרי שתתקנו את כל חוסר התאימות או ההבדלים האחרים שזיהיתם, תוכלו להתחיל לעדכן את הקוד כדי להשתמש בתחביר V8 ובתכונות אחרות.
  5. אחרי שמסיימים את ההתאמות בקוד, חשוב לבדוק את הסקריפט ביסודיות כדי לוודא שהוא פועל כמצופה.
  6. אם הסקריפט הוא אפליקציית אינטרנט או תוסף שפורסם, צריך ליצור גרסה חדשה של הסקריפט עם ההתאמות ל-V8, ולהגדיר את הפריסה לגרסה החדשה שנוצרה. כדי שהגרסה V8 תהיה זמינה למשתמשים, צריך לפרסם מחדש את הסקריפט עם הגרסה הזו.
  7. אם הסקריפט שלכם משמש כספרייה, צריך ליצור פריסה חדשה עם ניהול גרסאות של הסקריפט. צריך להודיע על הגרסה החדשה הזו לכל הסקריפטים ולכל המשתמשים שמשתמשים בספרייה, ולהנחות אותם לעדכן לגרסה שתומכת ב-V8. מוודאים שגרסאות ישנות יותר של הספרייה שמבוססות על Rhino לא נמצאות יותר בשימוש פעיל או בגישה.
  8. מוודאים שאף מופע של הסקריפט לא פועל יותר בסביבת זמן הריצה של Rhino מדור קודם. מוודאים שכל הפריסות משויכות לגרסה שנמצאת ב-V8. העברת פריסות ישנות לארכיון. בודקים את כל הגרסאות ומוחקים את הגרסאות שלא משתמשות ב-V8 Runtime.

חוסר תאימות

לצערנו, סביבת זמן הריצה המקורית של Apps Script שמבוססת על Rhino אפשרה כמה התנהגויות לא סטנדרטיות של 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(), שמחזיר שנה בת 4 ספרות ללא קשר לתאריך.

לא להשתמש במילות מפתח שמורות כשמות

ב-ECMAScript אסור להשתמש במילות מפתח שמורות מסוימות בשמות של פונקציות ומשתנים. סביבת הריצה של Rhino אפשרה שימוש בהרבה מהמילים האלה, ולכן אם אתם משתמשים בהן בקוד, אתם צריכים לשנות את השם של הפונקציות או המשתנים.

כשמעבירים סקריפט ל-V8, צריך להימנע מלתת למשתנים או לפונקציות שמות שכוללים אחת ממילות המפתח השמורות. כדי להימנע משימוש בשם מילת המפתח, צריך לשנות את השם של כל משתנה או פונקציה. דוגמאות לשימוש נפוץ במילות מפתח כשמות: class, import ו-export.

הימנעות מהקצאה מחדש של משתני 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__ באב-טיפוס של המחלקה. התכונה הזו נוספה גם לזמן הריצה של Rhino ב-Apps Script כדי להקל על המפתחים. עם זאת, התכונה הזו אף פעם לא הייתה חלק מתקן ECMA-262, והיא הוסרה ממנועי JavaScript שתואמים ל-ECMAScript. בסקריפטים שמשתמשים ב-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 מותנים 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().

הבדלים אחרים

בנוסף לחוסר התאימות שצוין למעלה, שעלול לגרום לכשלים בסקריפטים, יש עוד כמה הבדלים שאם לא יתוקנו, עלולים לגרום להתנהגות בלתי צפויה של סקריפטים בזמן הריצה של 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.fileName וב-Error.lineNumber

בזמן הריצה של V8, אובייקט JavaScript Error לא תומך ב-fileName או ב-lineNumber כפרמטרים של בנאי או כמאפיינים של אובייקט.

כשמעבירים את הסקריפט ל-V8, צריך להסיר את התלות ב-Error.fileName וב-Error.lineNumber.

אפשרות אחרת היא להשתמש ב-Error.prototype.stack. גם מחסנית זו אינה סטנדרטית, אך היא נתמכת ב-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)
      

שינוי הטיפול באובייקטים מסוג enum שהומרו למחרוזת

בזמן הריצה המקורי של Rhino, השימוש בשיטה JSON.stringify() של JavaScript באובייקט enum מחזיר רק {}.

בגרסה 8, שימוש באותה שיטה באובייקט enum מחזיר את שם ה-enum.

כשמעבירים את הסקריפט ל-V8, צריך לבדוק את הציפיות של הקוד לגבי הפלט של JSON.stringify() באובייקטים של 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"

שינוי הטיפול בפרמטרים לא מוגדרים

בזמן הריצה המקורי של 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. המשמעות היא שהפניות ל-thisglobal בקוד מוערכות למעשה להקשר המיוחד, שמכיל רק את הקוד והמשתנים שהוגדרו בסקריפט. השימוש ב-this לא כולל את השירותים המובנים של Apps Script ואת האובייקטים של ECMAScript. המצב הזה דומה למבנה 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 מובנים כמו Math ו-Date.

כשמעבירים את הסקריפט ל-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"

נניח שיש פרויקט א' ופרויקט ב', ופרויקט א' משתמש בפרויקט ב' כספרייה.

//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, העברת משאב לא משותף לספרייה פועלת. הספרייה משתמשת במשאב הלא משותף שהועבר.

אסור להעביר משאבים לא משותפים כפרמטרים של פונקציות. תמיד צריך להצהיר על משאבים לא משותפים באותו סקריפט שמשתמש בהם.

נניח שיש פרויקט א' ופרויקט ב', ופרויקט א' משתמש בפרויקט ב' כספרייה. בדוגמה הזו, 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 יפעלו בצורה תקינה, צריך להעניק למשתמשים לפחות הרשאת צפייה בסקריפט.