אם יש לכם סקריפט קיים שמשתמש בסביבת זמן הריצה של Rhino ואתם רוצים להשתמש בתחביר ובתכונות של V8, עליכם להעביר את הסקריפט ל-V8.
רוב הסקריפטים שנכתבו באמצעות סביבת זמן הריצה של Rhino יכולים לפעול בסביבת זמן ריצה של V8 ללא התאמה. לרוב, הדרישה היחידה להוספת תחביר ותכונות של V8 לסקריפט היא הפעלת סביבת זמן הריצה של V8.
עם זאת, יש קבוצה קטנה של אי-תאימות והבדלים אחרים שיכולים לגרום לכך שסקריפט נכשל או מתנהג באופן בלתי צפוי אחרי הפעלת סביבת זמן הריצה V8. כשעוברים להשתמש ב-V8 בסקריפט, צריך לחפש את הבעיות האלה בפרויקט הסקריפט ולתקן אותן.
תהליך ההעברה ל-V8
כדי להעביר סקריפט ל-V8, פועלים לפי השלבים הבאים:
- מפעילים את סביבת זמן הריצה V8 עבור הסקריפט.
- חשוב לקרוא בעיון את האי-תאימות שמפורטות בהמשך. בודקים את הסקריפט כדי לקבוע אם יש בו אי-תאימות. אם יש אי-תאימות אחת או יותר, משנים את קוד הסקריפט כדי להסיר את הבעיה או למנוע אותה.
- חשוב לקרוא בעיון את הבדלים האחרים שמפורטים בהמשך. בודקים את הסקריפט כדי לקבוע אם אחד מההבדלים המפורטים משפיע על התנהגות הקוד. משנים את הסקריפט כדי לתקן את ההתנהגות.
- אחרי שתתקנו את כל חוסר התאימות או ההבדלים האחרים שגיליתם, תוכלו להתחיל לעדכן את הקוד כך שישתמש בתחביר V8 ובתכונות אחרות לפי הצורך.
- אחרי שתסיימו לשנות את הקוד, כדאי לבדוק היטב את הסקריפט כדי לוודא שהוא פועל כצפוי.
- אם הסקריפט הוא אפליקציית אינטרנט או תוסף שפורסם, צריך ליצור גרסה חדשה של הסקריפט עם ההתאמות ל-V8. כדי שגרסת V8 תהיה זמינה למשתמשים, צריך לפרסם מחדש את הסקריפט עם הגרסה הזו.
חוסר תאימות
לצערנו, סביבת זמן הריצה המקורית של 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()
, שמחזיר שנה בת ארבע ספרות ללא קשר לתאריך.
הימנעות משימוש במילות מפתח שמורות כשמות
ב-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) בהתאמה אישית לכל כיתה על ידי הצהרה על פונקציית __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..if
לתנאי תפיסה (catch), כי הם לא תואמים לתקן.
כשמעבירים את הסקריפט ל-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()
גרסה 1.3 של JavaScript הכילה את השיטה Object.prototype.toSource(), שמעולם לא הייתה חלק מתקן ECMAScript. הוא לא נתמך בסביבת זמן הריצה של V8.
כשמעבירים את הסקריפט ל-V8, צריך להסיר מהקוד כל שימוש ב-Object.prototype.toSource().
הבדלים אחרים
בנוסף לאי-התאמות שלמעלה שעלולות לגרום לכשלים בסקריפטים, יש כמה הבדלים נוספים שיכולים לגרום להתנהגות בלתי צפויה של סקריפטים בסביבת זמן הריצה של V8, אם לא מתקנים אותם.
בקטעים הבאים מוסבר איך לעדכן את קוד הסקריפט כדי להימנע מהפתעות לא צפויות כאלה.
שינוי הפורמט של התאריך והשעה בהתאם לאזור
השיטות toLocaleString()
, toLocaleDateString()
ו-toLocaleTimeString()
ב-Date
פועלות באופן שונה בסביבת זמן הריצה של V8 בהשוואה ל-Rhino.
ב-Rhino, פורמט ברירת המחדל הוא הפורמט הארוך, וכל הפרמטרים המועברים מתעלמים.
בסביבת זמן הריצה של V8, פורמט ברירת המחדל הוא short format והטיפול בפרמטרים המועברים מתבצע בהתאם לתקן 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 untime, אובייקט ה-JavaScript הסטנדרטי Error
לא תומך ב-fileName
או ב-lineNumber
כפרמטרים של קונסטרוקטור או כמאפייני אובייקט.
כשמעבירים את הסקריפט ל-V8, צריך להסיר כל תלות ב-Error.fileName
וב-Error.lineNumber
.
אפשרות אחרת היא להשתמש ב-Error.prototype.stack
.
גם הסטאק הזה לא סטנדרטי, אבל יש תמיכה בו גם ב-Rhino וגם ב-V8. הפורמט של מעקב ה-stack שנוצר על ידי שתי הפלטפורמות שונה במקצת:
// 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, שימוש ב-method JSON.stringify()
של JavaScript באובייקט enum מחזיר רק את הערך {}
.
ב-V8, שימוש באותה שיטה באובייקט 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
בפועל. כלומר, הפניות ל-'this
הגלובלי' בקוד למעשה מקבלות ערך בהתאם להקשר המיוחד, שמכיל רק את הקוד והמשתנים שהוגדרו בסקריפט. השירותים המובנים של Apps Script ואובייקטים של 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, ההקשר המיוחד המשתמעים הוסר. משתנים ופונקציות גלובליים שמוגדרים בסקריפט ממוקמים בהקשר הגלובלי, לצד שירותי 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
היא להשתמש ב-constructor של a
במקרים שבהם אין צורך לחפש את כל שרשרת האב טיפוס, אלא רק לבדוק את ה-constructor.
שימוש: 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')); } |
// V8 runtime // Project A function testPassingNonSharedProperties() { PropertiesService.getScriptProperties() .setProperty('project', 'Project-A'); B.setScriptProperties(); // Prints: Project-A Logger.log(B.getScriptProperties( PropertiesService, 'project')); } |
עדכון הגישה לסקריפטים עצמאיים
כדי שהטריגרים של סקריפטים עצמאיים שפועלים בסביבת זמן הריצה V8 יפעלו כמו שצריך, צריך לתת למשתמשים לפחות הרשאת צפייה בסקריפט.