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