نقل النصوص البرمجية إلى وقت التشغيل V8

إذا كان لديك نص برمجي حالي يستخدم وقت تشغيل Rhino وأردت الاستفادة من بنية V8 وميزاته، يجب نقل النص البرمجي إلى V8.

يمكن أن تعمل معظم النصوص البرمجية التي تمت كتابتها باستخدام بيئة تشغيل Rhino باستخدام بيئة تشغيل V8 بدون تعديل. غالبًا ما يكون الشرط الوحيد لإضافة بنية V8 و ميزاته إلى نص برمجي هو تفعيل وقت تشغيل V8.

ومع ذلك، هناك مجموعة صغيرة من التوافقات والاختلافات الأخرى التي يمكن أن تؤدي إلى تعطُّل ملف برمجي أو سلوك غير متوقّع بعد تفعيل وقت تشغيل V8. عند نقل نص برمجي لاستخدام V8، يجب البحث في مشروع النص البرمجي عن هذه المشاكل وتصحيح أي مشاكل.

إجراء نقل البيانات إلى V8

لنقل نص برمجي إلى V8، اتّبِع الإجراء التالي:

  1. فعِّل وقت تشغيل V8 للنص البرمجي.
  2. راجِع بعناية حالات عدم التوافق المدرَجة أدناه. راجِع النص البرمجي لتحديد ما إذا كان يتضمّن أيًا من المشاكل المتعلقة بعدم التوافق. إذا كان يتضمّن مشكلة واحدة أو أكثر، عدِّل رمز النص البرمجي لإزالة المشكلة أو تجنّبها.
  3. راجِع الاختلافات الأخرى الواردة أدناه بعناية. افحص البرنامج النصي لتحديد ما إذا كان أي من الاختلافات المدرجة يؤثر على سلوك التعليمة البرمجية. عدِّل النص البرمجي لتصحيح السلوك.
  4. بعد تصحيح أيّ عدم توافق أو اختلافات أخرى تم رصدها، يمكنك البدء في تعديل الرمز البرمجي لاستخدام بنية V8 والميزات الأخرى على النحو المطلوب.
  5. بعد الانتهاء من تعديلات الرمز، اختبِر النص البرمجي بدقة للتأكّد من أنّه يعمل على النحو المتوقّع.
  6. إذا كان النص البرمجي تطبيق ويب أو إضافة منشورة، عليك إنشاء إصدار جديد من النص البرمجي باستخدام تعديلات 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'));
}

//Project B function setScriptProperties() { PropertiesService.getScriptProperties() .setProperty('project', 'Project-B'); } function getScriptProperties( propertiesService, key) { return propertiesService.getScriptProperties() .getProperty(key); }

// V8 runtime
// Project A
function testPassingNonSharedProperties() {
  PropertiesService.getScriptProperties()
      .setProperty('project', 'Project-A');
  B.setScriptProperties();
  // Prints: Project-A
  Logger.log(B.getScriptProperties(
      PropertiesService, 'project'));
}

// Project B function setProperties() { PropertiesService.getScriptProperties() .setProperty('project', 'Project-B'); } function getScriptProperties( propertiesService, key) { return propertiesService.getScriptProperties() .getProperty(key); }

تعديل إذن الوصول إلى النصوص البرمجية المستقلة

بالنسبة إلى النصوص البرمجية المستقلة التي تعمل على وقت تشغيل V8، عليك منح المستخدمين إذنًا بالاطّلاع على النص البرمجي على الأقل كي تعمل عوامل تشغيل النص البرمجي بشكلٍ سليم.