اگر یک اسکریپت موجود با استفاده از زمان اجرا 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)
به جاوا اسکریپت 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 برمی گرداند، اما سال های چهار رقمی را برای تاریخ های دیگر، که رفتار در جاوا اسکریپت 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__
توابع تکرار کننده سفارشی نسازید
جاوا اسکریپت 1.7 یک ویژگی اضافه کرد تا با اعلام یک تابع __iterator__
در نمونه اولیه آن کلاس، یک تکرار کننده سفارشی به هر کلاس اضافه شود. این همچنین به عنوان یک راحتی توسعهدهنده به برنامههای Apps Script's Rhino اضافه شد. با این حال، این ویژگی هرگز بخشی از استاندارد ECMA-262 نبود و در موتورهای جاوا اسکریپت سازگار با 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، هر شرط 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 حاوی یک متد Object.prototype.toSource() بود که هرگز بخشی از هیچ استاندارد ECMAScript نبود. در زمان اجرا V8 پشتیبانی نمی شود.
هنگام انتقال اسکریپت خود به V8، هرگونه استفاده از Object.prototype.toSource() را از کد خود حذف کنید .
تفاوت های دیگر
علاوه بر ناسازگاریهای بالا که میتواند باعث خرابی اسکریپت شود، چند تفاوت دیگر وجود دارد که اگر اصلاح نشود، ممکن است منجر به رفتار غیرمنتظره اسکریپت زمان اجرا V8 شود.
بخشهای زیر نحوه بهروزرسانی کد اسکریپت خود را برای جلوگیری از این غافلگیریهای غیرمنتظره توضیح میدهند.
قالب بندی تاریخ و زمان خاص محلی را تنظیم کنید
متدهای Date
toLocaleString()
, toLocaleDateString()
و toLocaleTimeString()
در زمان اجرا V8 در مقایسه با 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
خودداری کنید
در untime V8، شی استاندارد JavaScript Error
fileName
یا lineNumber
به عنوان پارامترهای سازنده یا ویژگی های شی پشتیبانی نمی کند.
هنگام انتقال اسکریپت خود به 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 اصلی، با استفاده از متد 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" |
کنترل پارامترهای تعریف نشده را تنظیم کنید
در Runtime اصلی 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
استفاده مستثنی هستند. این وضعیت مشابه این ساختار جاوا اسکریپت بود:
// 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"
پروژه A و پروژه B را در نظر بگیرید که در آن پروژه A از پروژه 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، ارسال یک منبع غیر مشترک به کتابخانه کار می کند. کتابخانه از منبع غیر اشتراکی تصویب شده استفاده می کند.
منابع غیر مشترک را به عنوان پارامترهای تابع ارسال نکنید. همیشه منابع غیر مشترک را در همان اسکریپتی که از آنها استفاده می کند، اعلام کنید.
پروژه A و پروژه B را در نظر بگیرید که در آن پروژه A از پروژه B به عنوان کتابخانه استفاده می کند. در این مثال، 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 اجرا میشوند، باید به کاربران اجازه دهید حداقل دسترسی به اسکریپت را مشاهده کنند تا محرکهای اسکریپت به درستی کار کنند.