The #ChromeDevSummit site is live, happening Nov 12-13 in San Francisco, CA
Check it out for details and request an invite. We'll be diving deep into modern web tech & looking ahead to the platform's future.

إضافة تفاعل مع جافا سكريبت

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

TL;DR

  • يمكن لجافا سكريبت الاستعلام عن كل من DOM وCSSOM وتعديلهما.
  • يؤدي تنفيذ جافا سكريبت إلى الحظر على CSSOM.
  • يحظر جافا سكريبت تركيبة DOM ما لم يتم الإفصاح عن عدم تزامنها.

تعد جافا سكريبت لغة ديناميكية تعمل في المتصفح وتتيح لنا تغيير كل شيء يتعلق بسلوك الصفحة: يمكننا تعديل المحتوى على الصفحة من خلال إضافة عناصر أو إزالتها من شجرة DOM، كما يمكننا تعديل خصائص CSSOM لكل عنصر، ويمكننا التعامل مع إدخال المستخدم، وغير ذلك الكثير. ولتوضيح ذلك عمليًا، يمكننا أن نزيد على مثال Hello World السابق نصًا برمجيًا مضمَّنًا وبسيطًا:

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link href="style.css" rel="stylesheet">
    <title>Critical Path: Script</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg"></div>
    <script>
      var span = document.getElementsByTagName('span')[0];
      span.textContent = 'interactive'; // change DOM text content
      span.style.display = 'inline';  // change CSSOM property
      // create a new element, style it, and append it to the DOM
      var loadTime = document.createElement('div');
      loadTime.textContent = 'You loaded this page on: ' + new Date();
      loadTime.style.color = 'blue';
      document.body.appendChild(loadTime);
    </script>
  </body>
</html>
  • يتيح لنا جافا سكريبت الوصول إلى DOM وسحب المرجع إلى عقدة الدورة المخفية، وقد لا تكون العقدة مرئية في شجرة العرض، ولكنها تظل موجودة في DOM! بعد ذلك وبمجرد أن يكون لدينا المرجع، يمكننا تغيير النص (عبر .textContent) وإلغاء خاصية نمط العرض المحسوبة من none إلى inline. وبعد أن يتم كل ذلك، ستظهر الصفحة **Hello interactive students!**.

  • يتيح لنا جافا سكريبت أيضًا إمكانية إنشاء عناصر وتنسيقها وإضافتها إلى DOM وإزالتها منها. وفي الحقيقة، قد تكون الصفحة بأكملها من الناحية التقنية ملف جافا سكريبت كبيرًا ينشئ العناصر وينسقها واحدًا تلو الآخر - وقد يكون هذا عمليًا، ولكن يعد استخدام HTML وCSS أكثر سهولة عمليًا. في الجزء الثاني من وظيفة جافا سكريبت ننشئ عنصر div جديدًا، ونعين المحتوى النصي للعنصر، ثم نضيفه إلى النص الأساسي.

معاينة الصفحة

بذلك نكون قد عدلنا المحتوى وتنسيق CSS لعقدة DOM حالية، وأضفنا عقدة جديدة تمامًا إلى المستند. ولن يفوز تصميم صفحتنا بأية جوائز، إلا أنه يوضح الإمكانيات والمرونة التي يمكن أن يوفرها جافا سكريبت.

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

أولاً، لاحظ أنه في المثال الوارد أعلاه يقع النص البرمجي المضمَّن أسفل الصفحة. لماذا? يجب أن تجرب بنفسك، ولكن عند نقل النص البرمجي أعلى العنصر the span، ستلاحظ أن النص البرمجي يسقط ويتبين أنه لم يتمكن من العثور على مرجع إلى أية عناصر span في المستند - أي getElementsByTagName('span') will return null. يوضح ذلك خاصية مهمة وهي أنه تم تنفيذ النص البرمجي في النقطة المحددة حيث تم إدراجه في المستند. عندما يواجه محلل HTML علامة نص برمجي، فإنه يعطِّل العملية مؤقتًا حتى إنشاء DOM والحصول على التحكم في محرك جافا سكريبت، وبعد انتهاء تشغيل محرك جافا سكريبت، يبدأ المتصفح من جديد من حيث انتهى ويستكمل إنشاء DOM.

بمعنى آخر، لا يمكن لحزمة النص البرمجي العثور على أية عناصر لاحقًا في الصفحة نظرًا لأنه لم تتم معالجتها بعد. وملخص ذلك في ما يلي: يحظر تنفيذ النص البرمجي المضمَّن إنشاء DOM، وقد يؤدي كذلك إلى تأخر العرض المبدئي.

وهناك خاصية بسيطة أخرى لوضع النصوص البرمجية في الصفحة وهي أنه لا يمكنها فقط قراءة وتعديل DOM، بل يمكنها أيضًا قراءة وتعديل خصائص CSSOM. وفي الحقيقة، هذا تحديدًا ما نفعله في المثال عند تغيير خاصية العرض لعنصر span من none إلى inline. وما النتيجة النهائية؟ في النهاية تصبح لدينا حالة تعارض.

ماذا لو لم ينته المتصفح من تنزيل وتصميم CSSOM عندما نرغب في تشغيل النص البرمجي؟ الإجابة بسيطة وليست جيدة للأداء: سيؤخر المتصفح تنفيذ النص البرمجي حتى الانتهاء من تنزيل CSSOM وإنشائه، وأثناء الانتظار يتم كذلك حظر إنشاء DOM.

باختصار، يوفر جافا سكريبت العديد من الملحقات الجديدة بين تنفيذ DOM وCSSOM وجافا سكريبت، ويمكن أن يؤدي إلى تأخر كبير في سرعة معالجة المتصفح وعرضه للصفحة على الشاشة:

  1. موقع النص البرمجي في المستند يمثل أهمية كبيرة.
  2. يتم تعطيل إنشاء DOM مؤقتًا عندما تتم مواجهة علامة النص البرمجي وحتى انتهاء تنفيذ النص البرمجي.
  3. يمكن لجافا سكريبت الاستعلام عن DOM وCSSOM وتعديلهما.
  4. يتم تأجيل تنفيذ جافا سكريبت حتى يصبح CSSOM جاهزًا.

عند الحديث عن تحسين مسار العرض الحرج، فإننا نتحدث إلى حد كبير عن استيعاب وتحسين الرسم البياني للعلاقة بين HTML وCSS وجافا سكريبت.

الفرق بين حظر المحلل وجافا سكريبت غير المتزامن

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

ماذا عن النصوص البرمجية المضمنة عبر علامة نص برمجي؟ دعونا نتناول المثال السابق ونستخلص الشفرة في ملف منفصل:

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link href="style.css" rel="stylesheet">
    <title>Critical Path: Script External</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg"></div>
    <script src="app.js"></script>
  </body>
</html>

app.js

var span = document.getElementsByTagName('span')[0];
span.textContent = 'interactive'; // change DOM text content
span.style.display = 'inline';  // change CSSOM property
// create a new element, style it, and append it to the DOM
var loadTime = document.createElement('div');
loadTime.textContent = 'You loaded this page on: ' + new Date();
loadTime.style.color = 'blue';
document.body.appendChild(loadTime);

هل تتوقع أن يكون ترتيب التنفيذ مختلفًا عندما نستخدم علامة <script> بدلاً من استخدام مقتطف جافا سكريبت مضمَّن؟ بالفعل ستكون الإجابة لا، نظرًا لأنهما متطابقان وسيتبعان سلوكًا واحدًا. وفي الحالتين سيتعين على المتصفح التوقف مؤقتًا وتنفيذ النص البرمجي قبل معالجة ما يتبقى من المستند. ولكن، في حالة ملف جافا سكريبت الخارجي، سيتعين على المتصفح أيضًا التوقف مؤقتًا والانتظار حتى يتم جلب النص البرمجي من القرص وذاكرة التخزين المؤقت والخادم البعيد، وهو ما يمكن أن يضيف عشرات الآلاف من الميلي ثانية من التأخير إلى مسار العرض الحرج.

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

إذًا، كيف نحقق ذلك؟ الأمر بسيط للغاية، حيث يمكننا وضع علامة على النص البرمجي باعتباره async:

<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link href="style.css" rel="stylesheet">
    <title>Critical Path: Script Async</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg"></div>
    <script src="app.js" async></script>
  </body>
</html>

وتساعد إضافة الكلمة الرئيسية غير المتزامنة إلى علامة النص البرمجي في إخبار المتصفح أنه يتعين عليه عدم حظر إنشاء DOM أثناء انتظار توفر النص البرمجي - وهذا مكسب كبير بالنسبة إلى الأداء.