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

عند نقل النص البرمجي إلى V8، تجنَّب استخدام الدالة __iterator__ لإنشاء مكرّرات مخصّصة. يمكنك بدلاً من ذلك استخدام المكررات رقم 6 لـ ECMAScript.

ضع في الاعتبار إنشاء الصفيفة التالية:

// 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.

في وحيد القرن، يكون التنسيق التلقائي هو التنسيق الطويل، ويتم تجاهل أي معلَمات يتم تمريرها.

في بيئة التشغيل 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.

عند نقل النص البرمجي إلى 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 العمومية" في الرمز البرمجي يتم تقييمها فعليًا وفقًا للسياق الخاص، الذي يحتوي فقط على الرمز والمتغيّرات المحددة في النص البرمجي. يتم استبعاد خدمات "برمجة تطبيقات Google" المُدمَجة وكائنات 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.
}();

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