تاريخ النشر: 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 وسحب المرجع إلى عقدة الامتداد المخفية؛ قد لا تكون العقدة مرئية في شجرة العرض، ولكنها لا تزال موجودة في نموذج العناصر في المستند (DOM). بعد ذلك، عندما يكون لدينا المرجع، يمكننا تغيير نصه (عبر .textContent)، وحتى إلغاء خاصية نمط العرض المحسوبة من "none" إلى "تضمين". تعرض صفحتنا الآن العبارة "مرحبًا بالطلاب التفاعليين".
تسمح لنا 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 أثناء انتظار توفّر البرنامج النصي، ما يمكن أن يؤدي إلى تحسين الأداء بشكل كبير.