אם יש לכם סקריפט שמשתמש בסביבת זמן הריצה של 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__
באב הטיפוס של אותו מחלקה. התכונה הזו נוספה גם לסביבת זמן הריצה של 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); } |
הימנעות מסעיפים מותנים של קליטה
סביבת זמן הריצה של V8 לא תומכת בתנאי קליטה מותנים של catch..if
כי הם לא תואמים לתקן.
כשמעבירים סקריפט ל-V8, צריך להעביר את תנאי הקליטה אל גוף השליפה:
// 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 הכילה method 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
כפרמטרים של constructor או כמאפייני אובייקט.
כשמעבירים סקריפט ל-V8, צריך להסיר את התלות ב-Error.fileName
וב-Error.lineNumber
.
לחלופין, אפשר להשתמש ב-Error.prototype.stack
.
גם המקבץ הזה לא סטנדרטי, אבל נתמך גם ב-Rhino וגם ב-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, שימוש ב-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
ל-method כפרמטר, הובילה להעברת המחרוזת "undefined"
ל-method הזה.
ב-V8, העברת undefined
ל-methods מקבילה להעברת 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, פרויקט והספריות שלו פועלים בהקשרים שונים של הפעלה, ולכן יש להם רשתות שונות של רשתות '{0/}' ו'שרשראות אבות טיפוס'.
שימו לב שזה המצב רק אם הספרייה משתמשת ב-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, צריך לספק למשתמשים הרשאת צפייה לפחות לסקריפט כדי שהטריגרים של הסקריפט יפעלו כראוי.