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

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

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

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

إجراء نقل البيانات إلى 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() للحصول على التفاصيل).

عند نقل النص البرمجي إلى الإصدار 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، يؤدي استخدام الطريقة نفسها على عنصر مصنّف إلى عرض اسم المصنّف.

عند نقل النص البرمجي إلى 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. كان هذا الموقف مشابهًا لهيكل برمجة شادٍّة التالي:

// 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 في سياق عام. في معظم الحالات، لا تظهر الاختلافات إلا إذا كان الرمز البرمجي يفحص مفاتيح أو أسماء السمات الخاصة بموضوع thisglobal:

// 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، عليك منح المستخدمين إذنًا بالاطّلاع على النص البرمجي على الأقل كي تعمل عوامل تشغيل النص البرمجي بشكلٍ سليم.