تاريخ النشر: 31 كانون الأول (ديسمبر) 2013
تتيح لنا JavaScript تعديل كل جانب من جوانب الصفحة تقريبًا: المحتوى، والتصميم، والاستجابة لتفاعل المستخدم. ومع ذلك، يمكن أن تحظر JavaScript أيضًا إنشاء نموذج كائن المستند (DOM) وتؤخّر عرض الصفحة. لتحقيق أفضل قياس ممكن لأداء المحتوى، يجب جعل JavaScript غير متزامن وإزالة أي JavaScript غير ضروري من مسار العرض الحرج.
ملخّص
- يمكن لـ JavaScript طلب نموذج DOM وCSSOM وتعديله.
- يتم حظر تنفيذ JavaScript في CSSOM.
- تحظر JavaScript إنشاء DOM ما لم يتم تحديدها صراحةً على أنّها غير متزامنة.
JavaScript هي لغة ديناميكية يتم تشغيلها في المتصفّح وتسمح لنا بتغيير كل جانب تقريبًا من سلوك الصفحة: يمكننا تعديل المحتوى من خلال إضافة عناصر وإزالتها من شجرة DOM، ويمكننا تعديل سمات CSSOM لكل عنصر، ويمكننا معالجة إدخال المستخدم، وغير ذلك الكثير. لتوضيح ذلك، اطّلِع على ما يحدث عند تغيير مثال "مرحبًا بك في العالم" السابق لإضافة نص برمجي مضمّن قصير:
<!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>
تسمح لنا JavaScript بالوصول إلى نموذج DOM وإزالة الإشارة إلى عقدة span المخفية. قد لا تكون العقدة مرئية في شجرة العرض، ولكنّها لا تزال موجودة في نموذج DOM. بعد ذلك، عندما نحصل على المرجع، يمكننا تغيير نصه (من خلال textContent.) وحتى إلغاء سمة نمط العرض المحسوبة من "none" إلى "inline". تعرض صفحتنا الآن العبارة "مرحبًا بالطلاب التفاعليين".
تسمح لنا JavaScript أيضًا بإنشاء عناصر جديدة في DOM وتنسيقها وإضافتها وإزالتها. من الناحية الفنية، يمكن أن تكون صفحتنا بأكملها ملف JavaScript كبيرًا واحدًا ينشئ العناصر ويصمّمها واحدًا تلو الآخر. على الرغم من أنّ هذا الإجراء قد يكون مناسبًا، إلا أنّ استخدام HTML وCSS أسهل بكثير. في الجزء الثاني من دالة JavaScript، ننشئ عنصر div جديدًا ونضبط محتوى النص وننسقه ونلحقه بالنص الأساسي.
بعد ذلك، عدّلنا محتوى عقدة DOM الحالية وأسلوب CSS الخاص بها، وأضفنا عقدة جديدة تمامًا إلى المستند. لن تفوز صفحتنا بأي جوائز تصميم، ولكنها توضّح القوة والمرونة التي يوفّرها لنا JavaScript.
ومع ذلك، على الرغم من أنّ لغة JavaScript تمنحنا الكثير من الإمكانيات، إلا أنّها تفرض الكثير من القيود الإضافية على كيفية عرض الصفحة ووقت عرضها.
أولاً، لاحظ أنّ النص البرمجي المضمّن في المثال السابق يقع بالقرب من أسفل الصفحة. لماذا؟ عليك تجربته بنفسك، ولكن إذا نقلنا النص البرمجي فوق العنصر <span>
، ستلاحظ أنّ النص البرمجي يتعذّر عليه العثور على إشارة إلى أي عناصر <span>
في المستند، أي أنّ getElementsByTagName('span')
يعرض null
. يوضّح ذلك خاصيّة مهمة: يتم تنفيذ النص البرمجي في النقطة المحدّدة التي يتم إدراجه فيها في المستند. عندما يصادف منظِّم HTML علامة نص برمجي، يوقف مؤقتًا عملية إنشاء DOM ويمنح التحكّم في محرك JavaScript. وبعد انتهاء تشغيل محرك JavaScript، يستأنف المتصفّح العمل من حيث توقّفه ويواصل إنشاء DOM.
بعبارة أخرى، لا يمكن لوحدة النص البرمجي العثور على أي عناصر في وقت لاحق من الصفحة لأنّه لم تتم معالجتها بعد. بعبارة أخرى، يؤدي تنفيذ النص البرمجي المضمّن إلى حظر إنشاء DOM، ما يؤخّر أيضًا العرض الأولي.
من الخصائص الدقيقة الأخرى لاستخدام النصوص البرمجية في صفحتنا أنّها لا يمكنها قراءة DOM وتعديله فقط، بل يمكنها أيضًا قراءة وتعديل خصائص CSSOM. في الواقع، هذا هو ما نفعله بالضبط في مثالنا عندما نغيّر سمة العرض لعنصر span من none إلى inline. ما هي النتيجة النهائية؟ لدينا الآن حالة تسابق.
ماذا لو لم ينتهِ المتصفّح من تنزيل CSSOM وإنشاءه عندما نريد تشغيل النص البرمجي؟ لا تؤدي هذه الطريقة إلى تحسين الأداء: يؤخّر المتصفّح تنفيذ النصوص البرمجية وإنشاء DOM إلى أن ينتهي من تنزيل CSSOM وإنشاءه.
باختصار، تُعرِض JavaScript الكثير من التبعيات الجديدة بين DOM وCSSOM وتنفيذ JavaScript. يمكن أن يؤدي ذلك إلى تأخيرات كبيرة في المتصفّح أثناء معالجة الصفحة وعرضها على الشاشة:
- إنّ موقع النص البرمجي في المستند مهم.
- عندما يصادف المتصفّح علامة نصّ برمجي، يتمّ إيقاف إنشاء DOM مؤقتًا إلى أن تنتهي عملية تنفيذ النصّ البرمجي.
- يمكن لـ JavaScript طلب نموذج DOM وCSSOM وتعديله.
- يتم إيقاف تنفيذ JavaScript مؤقتًا إلى أن يصبح CSSOM جاهزًا.
يشير "تحسين مسار المعالجة المُهمّ" إلى حدٍ كبير إلى فهم الرسم البياني للتبعية بين HTML وCSS وJavaScript وتحسينه.
حظر المُحلِّل مقابل JavaScript غير المتزامن
يتم تنفيذ JavaScript تلقائيًا "بطريقة حظر المحلل اللغوي": عندما يصادف المتصفّح نصًا برمجيًا في المستند، يجب أن يوقف مؤقتًا إنشاء DOM ويسلّم التحكّم إلى وقت تشغيل JavaScript ويسمح بتنفيذ النص البرمجي قبل المتابعة في إنشاء 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> أو مقتطف JavaScript مضمّنًا، تتوقع أن يعمل كلاهما بالطريقة نفسها. وفي كلتا الحالتَين، يوقف المتصفّح النص البرمجي مؤقتًا وينفّذه قبل أن يتمكّن من معالجة الجزء المتبقّي من المستند. ومع ذلك، في حال استخدام ملف JavaScript خارجي، يجب أن يتوقف المتصفّح مؤقتًا لمحاولة جلب النص البرمجي من القرص أو ذاكرة التخزين المؤقت أو خادم بعيد، ما يمكن أن يؤدي إلى تأخير يتراوح بين عشرات وآلاف المللي ثانية في مسار المعالجة المُهمّ.
يتم تلقائيًا حظر جميع وحدات JavaScript من خلال التحليل. وبما أنّ المتصفّح لا يعرف ما الذي ينوي النص البرمجي تنفيذه على الصفحة، يفترض المتصفّح أسوأ سيناريو ويحظّر المُحلِّل. إنّ إرسال إشارة إلى المتصفّح بأنّه لا يلزم تنفيذ النص البرمجي في النقطة المحدّدة التي تتم الإشارة إليها تسمح للمتصفّح بمواصلة إنشاء DOM والسماح بتنفيذ النص البرمجي عندما يصبح جاهزًا، على سبيل المثال، بعد جلب الملف من ذاكرة التخزين المؤقت أو من خادم بعيد.
لتحقيق ذلك، تتم إضافة السمة async
إلى العنصر <script>
:
<!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>
تُعلِم إضافة الكلمة الرئيسية async إلى علامة البرنامج النصي المتصفّح بعدم حظر إنشاء DOM أثناء انتظار توفّر البرنامج النصي، ما يمكن أن يؤدي إلى تحسين الأداء بشكل كبير.