إذا كان لديك نص برمجي حالي يستخدم وقت تشغيل Rhino وتريد الاستفادة من بنية وميزات V8، عليك نقل النص البرمجي إلى V8.
يمكن تشغيل معظم النصوص البرمجية المكتوبة باستخدام وقت تشغيل Rhino باستخدام وقت تشغيل V8 بدون إجراء أي تعديلات. غالبًا ما يكون الشرط الوحيد لإضافة بنية V8 و ميزاته إلى نص برمجي هو تفعيل وقت تشغيل V8.
ومع ذلك، هناك مجموعة صغيرة من التوافقات والاختلافات الأخرى التي يمكن أن تؤدي إلى تعطُّل ملف برمجي أو سلوك غير متوقّع بعد تفعيل وقت تشغيل V8. أثناء نقل نص برمجي لاستخدام الإصدار 8، عليك البحث في مشروع النص البرمجي عن هذه المشاكل و تصحيح أي مشاكل تعثر عليها.
إجراء نقل البيانات إلى 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()
للحصول على التفاصيل).
عند نقل النص البرمجي إلى الإصدار 8 من 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 إلى عرض القيمة {}
فقط.
في الإصدار 8، يؤدي استخدام الطريقة نفسها على عنصر مصنّف إلى عرض اسم المصنّف.
عند نقل النص البرمجي إلى الإصدار 8 من JavaScript،
اختبِر توقّعات الرمز البرمجي بشأن ناتج
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
. كان هذا الموقف مشابهًا لهيكل برمجة شادٍّ التالية:
// 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، تتم إزالة السياق الخاص الضمني. يتم وضع المتغيّرات والدوالّ الكلية
المحدّدة في النص البرمجي في السياق العام، بجانب خدمات
Apps Script المدمجة ووظائف ECMAScript المدمجة، مثل Math
وDate
.
عند نقل النص البرمجي إلى الإصدار 8، اختبِر توقعات الرمز البرمجي وعدِّلها
بشأن استخدام 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، عليك منح المستخدمين إذنًا بالاطّلاع على النص البرمجي على الأقل لكي تعمل عوامل تشغيل النص البرمجي بشكلٍ سليم.