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

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

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

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

إجراء النقل V8

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

  1. يجب تفعيل وقت تشغيل V8 للنص البرمجي.
  2. يُرجى مراجعة العناصر غير المتوافقة المذكورة أدناه بعناية. افحص النص البرمجي لتحديد ما إذا كان هناك أي من العناصر غير المتوافقة؛ في حال وجود حالة عدم توافق واحدة أو أكثر، عدِّل رمز النص البرمجي لإزالة المشكلة أو تجنّبها.
  3. راجِع الاختلافات الأخرى الواردة أدناه بعناية. افحص النص البرمجي لتحديد ما إذا كان أي من الاختلافات المدرجة يؤثر في سلوك الرمز. يُرجى تعديل النص البرمجي لتصحيح السلوك.
  4. بعد تصحيح أي نقاط عدم توافق تم اكتشافها أو اختلافات أخرى، يمكنك البدء في تحديث التعليمة البرمجية لاستخدام بنية V8 والميزات الأخرى كما تريد.
  5. بعد الانتهاء من تعديلات التعليمة البرمجية، اختبر النص البرمجي بدقة للتأكد من أنه يعمل كما هو متوقع.
  6. إذا كان النص البرمجي عبارة عن تطبيق ويب أو إضافة منشورة، يجب إنشاء نسخة جديدة من النص البرمجي باستخدام تعديلات V8. لإتاحة الإصدار V8 للمستخدمين، يجب إعادة نشر النص البرمجي مع هذا الإصدار.

حالات عدم التوافق

كان من الأسف يسمح وقت تشغيل "برمجة تطبيقات Google" المستند إلى Rhino بعدة سلوكيات غير عادية في ECMAScript. نظرًا لأن V8 يتوافق مع المعايير، فلا يتم دعم هذه السلوكيات بعد الترحيل. ويؤدي الفشل في تصحيح هذه المشكلات إلى حدوث أخطاء أو سلوك نص برمجي معطل بعد تفعيل وقت تشغيل V8.

تصف الأقسام التالية كل من هذه السلوكيات والخطوات التي يجب عليك اتخاذها لتصحيح رمز النص البرمجي أثناء النقل إلى V8.

تجنُّب 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.

عند نقل النص البرمجي إلى 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 تتيح لمشاريع "برمجة تطبيقات Google" استخدام بنية 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 ميزة تسمح بإضافة مكرّر مخصّص إلى أي clas من خلال الإعلان عن دالة __iterator__ في النموذج الأولي لهذه الفئة. تمت إضافة هذه الميزة أيضًا إلى وقت تشغيل Rhino في لغة "برمجة تطبيقات Google" لتسهيل الأمر على المطوّرين. ومع ذلك، لم تكن هذه الميزة أبدًا جزءًا من معيار 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، لا يتيح كائن Error العادي في JavaScript استخدام 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() على كائن تعداد، يتم عرض {} فقط.

في V8، يؤدي استخدام نفس الطريقة على كائن التعداد إلى إعادة ربط اسم التعداد.

عند نقل النص البرمجي إلى 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.

عند نقل النص البرمجي إلى 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 العام" في الرمز يتم تقييمها فعليًا للسياق الخاص الذي يحتوي فقط على الرمز والمتغيرات المحددة في النص البرمجي. يتم استبعاد خدمات "برمجة التطبيقات" المضمّنة وكائنات 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، يجب أن تمنح المستخدمين على الأقل الإذن بالاطّلاع على النص البرمجي لكي تعمل مشغِّلات النص البرمجي بشكل صحيح.