لماذا يجب عليك إعادة استخدام المنسقات بدلاً من إنشاء منسقات جديدة؟

إنشاء منسقات Intl مكلف، لكن إعادة استخدام نفس نسخة المنسق يحسن الأداء

مقدمة

عندما تنشئ منسق Intl في JavaScript، يقوم المتصفح بإجراء عمليات مكلفة لإعداد نسخة المنسق. يحلل خياراتك، ويحمل بيانات اللغة من القرص، ويبني هياكل البيانات الداخلية للتنسيق. إذا أنشأت منسقاً جديداً في كل مرة تحتاج فيها إلى تنسيق قيمة، فإنك تكرر هذا العمل المكلف دون داعٍ.

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

يمكن أن يكون الفرق في الأداء بين إنشاء منسقات جديدة وإعادة استخدام المنسقات الموجودة كبيراً. في السيناريوهات النموذجية، يمكن أن تقلل إعادة استخدام المنسقات وقت التنسيق من مئات الميلي ثانية إلى بضعة ميلي ثوانٍ فقط.

لماذا يعتبر إنشاء المنسقات مكلفاً

يتضمن إنشاء منسق Intl عدة عمليات مكلفة تحدث داخل المتصفح.

أولاً، يحلل المتصفح الخيارات التي تقدمها ويتحقق من صحتها. يتحقق من أن معرفات اللغة صالحة، وأن الخيارات الرقمية ضمن النطاق، وأن الخيارات غير المتوافقة لا يتم دمجها. يتطلب هذا التحقق عمليات تحليل السلاسل النصية والبحث.

ثانياً، يجري المتصفح التفاوض على اللغة. يأخذ اللغة المطلوبة ويجد أفضل تطابق متاح من اللغات التي يدعمها المتصفح. يتضمن ذلك مقارنة معرفات اللغة وتطبيق قواعد الاحتياطية.

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

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

كل هذا العمل يحدث في كل مرة تقوم فيها بإنشاء منسق. إذا قمت بإنشاء منسق، واستخدمته مرة واحدة، ثم تجاهلته، فإنك تهدر كل عمل الإعداد هذا.

الفرق في الأداء

يصبح تأثير إعادة إنشاء المنسقات على الأداء واضحاً عندما تقوم بتنسيق العديد من القيم.

لنأخذ في الاعتبار كوداً يقوم بتنسيق قائمة من التواريخ دون إعادة استخدام المنسق.

const dates = [
  new Date('2024-01-15'),
  new Date('2024-02-20'),
  new Date('2024-03-10')
];

// Creates a new formatter for each date
dates.forEach(date => {
  const formatted = date.toLocaleDateString('en-US', {
    year: 'numeric',
    month: 'long',
    day: 'numeric'
  });
  console.log(formatted);
});

تقوم الدالة toLocaleDateString() بإنشاء نسخة جديدة من DateTimeFormat داخلياً لكل تاريخ تقوم بتنسيقه. لثلاثة تواريخ، ينشئ هذا ثلاثة منسقات. لألف تاريخ، ينشئ ألف منسق.

قارن هذا بكود ينشئ منسقاً واحداً ويعيد استخدامه.

const dates = [
  new Date('2024-01-15'),
  new Date('2024-02-20'),
  new Date('2024-03-10')
];

// Create formatter once
const formatter = new Intl.DateTimeFormat('en-US', {
  year: 'numeric',
  month: 'long',
  day: 'numeric'
});

// Reuse formatter for each date
dates.forEach(date => {
  const formatted = formatter.format(date);
  console.log(formatted);
});

ينشئ هذا الكود منسقاً واحداً ويستخدمه ثلاث مرات. لألف تاريخ، لا يزال ينشئ منسقاً واحداً ويستخدمه ألف مرة.

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

إعادة استخدام المنسقات على مستوى الوحدة

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

// Create formatter at module scope
const dateFormatter = new Intl.DateTimeFormat('en-US', {
  year: 'numeric',
  month: 'long',
  day: 'numeric'
});

function formatDate(date) {
  return dateFormatter.format(date);
}

function formatDates(dates) {
  return dates.map(date => dateFormatter.format(date));
}

// All functions share the same formatter instance
console.log(formatDate(new Date()));
console.log(formatDates([new Date(), new Date()]));

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

يعمل نفس النمط مع منسقات الأرقام ومنسقات القوائم وجميع منسقات Intl الأخرى.

const numberFormatter = new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'USD'
});

const listFormatter = new Intl.ListFormat('en-US', {
  style: 'long',
  type: 'conjunction'
});

function formatPrice(amount) {
  return numberFormatter.format(amount);
}

function formatNames(names) {
  return listFormatter.format(names);
}

إعادة استخدام المنسقات في الدوال

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

function createDateFormatter() {
  const formatter = new Intl.DateTimeFormat('en-US', {
    year: 'numeric',
    month: 'long',
    day: 'numeric'
  });

  return function formatDate(date) {
    return formatter.format(date);
  };
}

const formatDate = createDateFormatter();

// The formatter is created once when you call createDateFormatter
// Each call to formatDate reuses the same formatter
console.log(formatDate(new Date('2024-01-15')));
console.log(formatDate(new Date('2024-02-20')));
console.log(formatDate(new Date('2024-03-10')));

هذا النمط مفيد عندما تريد إنشاء منسق مُهيأ يتم إعادة استخدامه ولكن لا تريد كشف المنسق نفسه.

متى تكون إعادة استخدام المنسقات أكثر أهمية

توفر إعادة استخدام المنسقات أكبر فائدة في سيناريوهات محددة.

السيناريو الأول هو الحلقات. إذا قمت بتنسيق القيم داخل حلقة، فإن إنشاء منسق جديد في كل تكرار يضاعف التكلفة بعدد التكرارات.

// Inefficient: creates N formatters
for (let i = 0; i < 1000; i++) {
  const formatted = new Intl.NumberFormat('en-US').format(i);
  processValue(formatted);
}

// Efficient: creates 1 formatter
const formatter = new Intl.NumberFormat('en-US');
for (let i = 0; i < 1000; i++) {
  const formatted = formatter.format(i);
  processValue(formatted);
}

السيناريو الثاني هو الدوال المستدعاة بشكل متكرر. إذا كانت الدالة تنسق القيم ويتم استدعاؤها عدة مرات، فإن إعادة استخدام المنسق تتجنب إعادة إنشاء المنسق في كل استدعاء.

// Inefficient: creates formatter on every call
function formatCurrency(amount) {
  const formatter = new Intl.NumberFormat('en-US', {
    style: 'currency',
    currency: 'USD'
  });
  return formatter.format(amount);
}

// Efficient: creates formatter once
const currencyFormatter = new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'USD'
});

function formatCurrency(amount) {
  return currencyFormatter.format(amount);
}

السيناريو الثالث هو معالجة مجموعات البيانات الكبيرة. عندما تقوم بتنسيق مئات أو آلاف القيم، تصبح تكلفة إعداد إنشاء المنسقات جزءاً كبيراً من إجمالي الوقت.

// Inefficient for large datasets
function processRecords(records) {
  return records.map(record => ({
    date: new Intl.DateTimeFormat('en-US').format(record.date),
    amount: new Intl.NumberFormat('en-US', {
      style: 'currency',
      currency: 'USD'
    }).format(record.amount)
  }));
}

// Efficient for large datasets
const dateFormatter = new Intl.DateTimeFormat('en-US');
const amountFormatter = new Intl.NumberFormat('en-US', {
  style: 'currency',
  currency: 'USD'
});

function processRecords(records) {
  return records.map(record => ({
    date: dateFormatter.format(record.date),
    amount: amountFormatter.format(record.amount)
  }));
}

في هذه السيناريوهات، تقلل إعادة استخدام المنسقات الوقت المستغرق في عمليات التنسيق وتحسن استجابة التطبيق.

تخزين المنسقات مؤقتاً بخيارات مختلفة

عندما تحتاج إلى استخدام منسقات بمجموعات خيارات مختلفة عديدة، يمكنك تخزين المنسقات مؤقتاً بناءً على تكوينها.

const formatterCache = new Map();

function getNumberFormatter(locale, options) {
  // Create a cache key from locale and options
  const key = JSON.stringify({ locale, options });

  // Return cached formatter if it exists
  if (formatterCache.has(key)) {
    return formatterCache.get(key);
  }

  // Create new formatter and cache it
  const formatter = new Intl.NumberFormat(locale, options);
  formatterCache.set(key, formatter);
  return formatter;
}

// First call creates and caches formatter
const formatter1 = getNumberFormatter('en-US', { style: 'currency', currency: 'USD' });
console.log(formatter1.format(42.50));

// Second call reuses cached formatter
const formatter2 = getNumberFormatter('en-US', { style: 'currency', currency: 'USD' });
console.log(formatter2.format(99.99));

// Different options create and cache a new formatter
const formatter3 = getNumberFormatter('en-US', { style: 'percent' });
console.log(formatter3.format(0.42));

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

تحسينات المتصفحات الحديثة

قامت محركات JavaScript الحديثة بتحسين إنشاء منسقات Intl لتقليل تكلفة الأداء. أصبح إنشاء المنسقات أسرع اليوم مما كان عليه في المتصفحات القديمة.

ومع ذلك، تظل إعادة استخدام المنسقات أفضل ممارسة. حتى مع التحسينات، لا يزال إنشاء منسق أكثر تكلفة من استدعاء طريقة format() على منسق موجود. الفرق في التكلفة أصغر مما كان عليه، لكنه لا يزال موجوداً.

في الكود عالي الأداء، والكود الذي يعمل في حلقات، والكود الذي يعالج مجموعات بيانات كبيرة، تستمر إعادة استخدام المنسقات في توفير فوائد قابلة للقياس. تحسين إنشاء المنسقات لا يلغي قيمة إعادة استخدام المنسقات.

النقاط الرئيسية

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

إعادة استخدام نسخ المنسقات تتجنب تكرار هذا العمل. تنشئ المنسق مرة واحدة وتستدعي طريقة format() الخاصة به مرات عديدة. هذا يقلل الوقت المستغرق في عمليات التنسيق.

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

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