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