إذا كان لديك نص برمجي حالي يستخدم وقت تشغيل Rhino وأردت الاستفادة من بنية V8 وميزاته، يجب نقل النص البرمجي إلى V8.
يمكن أن تعمل معظم النصوص البرمجية التي تمت كتابتها باستخدام بيئة تشغيل Rhino باستخدام بيئة تشغيل V8 بدون تعديل. غالبًا ما يكون الشرط الوحيد لإضافة بنية V8 و ميزاته إلى نص برمجي هو تفعيل وقت تشغيل V8.
ومع ذلك، هناك مجموعة صغيرة من التوافقات والاختلافات الأخرى التي يمكن أن تؤدي إلى تعطُّل ملف برمجي أو سلوك غير متوقّع بعد تفعيل وقت تشغيل V8. عند نقل نص برمجي لاستخدام V8، يجب البحث في مشروع النص البرمجي عن هذه المشاكل وتصحيح أي مشاكل.
إجراء نقل البيانات إلى V8
لنقل نص برمجي إلى V8، اتّبِع الإجراء التالي:
- فعِّل وقت تشغيل V8 للنص البرمجي.
- راجِع بعناية حالات عدم التوافق المدرَجة أدناه. راجِع النص البرمجي لتحديد ما إذا كان يتضمّن أيًا من المشاكل المتعلقة بعدم التوافق. إذا كان يتضمّن مشكلة واحدة أو أكثر، عدِّل رمز النص البرمجي لإزالة المشكلة أو تجنّبها.
- راجِع الاختلافات الأخرى الواردة أدناه بعناية. افحص البرنامج النصي لتحديد ما إذا كان أي من الاختلافات المدرجة يؤثر على سلوك التعليمة البرمجية. عدِّل النص البرمجي لتصحيح السلوك.
- بعد تصحيح أيّ عدم توافق أو اختلافات أخرى تم رصدها، يمكنك البدء في تعديل الرمز البرمجي لاستخدام بنية V8 والميزات الأخرى على النحو المطلوب.
- بعد الانتهاء من تعديلات الرمز، اختبِر النص البرمجي بدقة للتأكّد من أنّه يعمل على النحو المتوقّع.
- إذا كان النص البرمجي تطبيق ويب أو إضافة منشورة، عليك إنشاء إصدار جديد من النص البرمجي باستخدام تعديلات V8. لإتاحة إصدار V8 للمستخدمين، يجب إعادة نشر النص البرمجي باستخدام هذا الإصدار.
حالات عدم التوافق
كان وقت تشغيل Apps Script الأصلي المستنِد إلى Rhino يسمح بالعديد من سلوكيات ECMAScript غير العادية. وبما أنّ الإصدار 8 متوافق مع المعايير، لا يمكن استخدام هذه السلوكيات بعد نقل البيانات. يؤدي عدم تصحيح هذه المشاكل إلى حدوث أخطاء أو سلوك غير صحيح للنص البرمجي بعد تفعيل وقت تشغيل V8.
توضّح الأقسام التالية كلًّا من هذه السلوكيات والخطوات التي يجب اتّخاذها لتصحيح رمز النص البرمجي أثناء نقل البيانات إلى الإصدار 8.
تجنب 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.
عند نقل النص البرمجي إلى الإصدار 8، استخدِم دائمًا Date.prototype.getFullYear()
،
الذي يعرض سنة من أربعة أرقام بغض النظر عن التاريخ.
تجنَّب استخدام الكلمات الرئيسية المحجوزة كأسماء.
يحظر ECMAScript استخدام كلمات رئيسية محجوزة معيّنة في أسماء الدوال والمتغيّرات. كان وقت تشغيل Rhino يسمح باستخدام العديد من هذه الكلمات، ولذلك إذا كانت التعليمات البرمجية تستخدمها، عليك إعادة تسمية الدوال أو المتغيّرات.
عند نقل النص البرمجي إلى V8، تجنَّب تسمية المتغيّرات أو الدوالّ
باستخدام إحدى
الكلمات الرئيسية المحجوزة.
أعِد تسمية أي متغيّر أو دالة لتجنُّب استخدام اسم الكلمة الرئيسية. تشمل الاستخدامات الشائعة
للكلمات الرئيسية كأسماء ما يلي: class
وimport
وexport
.
تجنَّب إعادة تعيين متغيّرات const
.
في وقت تشغيل Rhino الأصلي، يمكنك الإعلان عن متغيّر باستخدام const
، ما يعني أنّ قيمة الرمز لا تتغيّر أبدًا ويتم تجاهل عمليات الربط المستقبلية بالرمز.
في وقت التشغيل الجديد V8، تكون الكلمة الرئيسية const
متوافقة مع المعيار، ويؤدي تحديد
قيمة لمتغيّر تمّ الإعلان عنه على أنّه const
إلى خطأ أثناء التشغيل
TypeError: Assignment to constant variable
.
عند نقل النص البرمجي إلى الإصدار 8، لا تحاول إعادة تعيين قيمة
متغيّر 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 لمشاريع "برمجة التطبيقات" باستخدام بنية 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 يتضمّن 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
كمعلَمات دالة إنشاء أو خصائص عناصر.
عند نقل النص البرمجي إلى 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) |
تعديل معالجة كائنات التعداد التي تم تحويلها إلى سلسلة
في وقت تشغيل Rhino الأصلي، يؤدي استخدام طريقة JavaScript
JSON.stringify()
على كائن enum إلى عرض {}
فقط.
في V8، يؤدي استخدام نفس الطريقة على كائن enum إلى إعادة تصنيف اسم التعداد.
عند نقل النص البرمجي إلى V8،
اختبِر توقّعات الرمز البرمجي بشأن ناتج
JSON.stringify()
على عناصر التعداد وعدِّلها:
// 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
.
عند نقل النص البرمجي إلى الإصدار 8،
اختبِر توقعات الرمز البرمجي بشأن 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. }(); |
في الإصدار 8، تتم إزالة السياق الخاص الضمني. يتم وضع المتغيرات والدوال العامة
المحددة في النص البرمجي في سياق عام، إلى جانب خدمات "برمجة تطبيقات Google" المُدمَجة
وأداة 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"
لنفترض أنّ المشروع "أ" يستخدم المشروع "ب" كمكتبة.
//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، عليك منح المستخدمين إذنًا بالاطّلاع على النص البرمجي على الأقل كي تعمل عوامل تشغيل النص البرمجي بشكلٍ سليم.