انتقال اسکریپت ها به زمان اجرا 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 اجازه داد. از آنجایی که V8 با استانداردها سازگار است، این رفتارها پس از مهاجرت پشتیبانی نمی شوند. عدم تصحیح این مشکلات منجر به خطا یا رفتار اسکریپت شکسته پس از فعال شدن زمان اجرا V8 می شود.

بخش‌های زیر هر یک از این رفتارها و مراحلی را که باید برای تصحیح کد اسکریپت خود در حین انتقال به V8 انجام دهید، شرح می‌دهد.

اجتناب for each(variable in object)

عبارت for each (variable in object) به جاوا اسکریپت 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 برمی گرداند، اما سال های چهار رقمی را برای تاریخ های دیگر، که رفتار در جاوا اسکریپت 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 به پروژه های Apps Script اجازه می دهد تا مستقیماً از نحو 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__ توابع تکرار کننده سفارشی نسازید

جاوا اسکریپت 1.7 یک ویژگی اضافه کرد تا با اعلام یک تابع __iterator__ در نمونه اولیه آن کلاس، یک تکرار کننده سفارشی به هر کلاس اضافه شود. این همچنین به عنوان یک راحتی توسعه‌دهنده به برنامه‌های Apps Script's Rhino اضافه شد. با این حال، این ویژگی هرگز بخشی از استاندارد ECMA-262 نبود و در موتورهای جاوا اسکریپت سازگار با 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، هر شرط catch را به داخل بدنه catch منتقل کنید :

// 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() خودداری کنید

جاوا اسکریپت 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 خودداری کنید

در untime 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)
      

مدیریت اشیاء enum رشته ای را تنظیم کنید

در زمان اجرای Rhino اصلی، با استفاده از متد JSON.stringify() جاوا اسکریپت روی یک شی enum فقط {} را برمی گرداند.

در V8، استفاده از روش مشابه در یک شی enum، نام enum را مجددا تنظیم می کند.

هنگام انتقال اسکریپت خود به V8، انتظارات کد خود را در مورد خروجی JSON.stringify() روی اشیاء enum آزمایش و تنظیم کنید :

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

کنترل پارامترهای تعریف نشده را تنظیم کنید

در Runtime اصلی 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 جهانی" در کد در واقع به زمینه خاصی که فقط شامل کد و متغیرهای تعریف شده در اسکریپت است، ارزیابی می شود. سرویس‌های 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.
}();

در V8، زمینه خاص ضمنی حذف می شود. متغیرها و توابع کلی تعریف شده در اسکریپت در زمینه جهانی، در کنار سرویس‌های داخلی Apps Script و 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"

پروژه A و پروژه B را در نظر بگیرید که در آن پروژه A از پروژه 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، ارسال یک منبع غیر مشترک به کتابخانه کار می کند. کتابخانه از منبع غیر اشتراکی تصویب شده استفاده می کند.

منابع غیر مشترک را به عنوان پارامترهای تابع ارسال نکنید. همیشه منابع غیر مشترک را در همان اسکریپتی که از آنها استفاده می کند، اعلام کنید.

پروژه A و پروژه B را در نظر بگیرید که در آن پروژه A از پروژه B به عنوان کتابخانه استفاده می کند. در این مثال، 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 اجرا می‌شوند، باید به کاربران اجازه دهید حداقل دسترسی به اسکریپت را مشاهده کنند تا محرک‌های اسکریپت به درستی کار کنند.