المستوى: مبتدئ — مناسب لو لسه بادئ في تحسين أداء صفحات الويب، والمقال بيشرح كل مصطلح بمثال قبل التعريف العلمي.
في نهاية المقال هتعرف تنقل سطر <script> واحد وتخلّي صفحتك تظهر للمستخدم أسرع بحوالي الثلثين، من غير ما تلمس أي كود جافاسكريبت جوّه الملف. ده أرخص تحسين ممكن تعمله لأداء موقعك.
المشكلة باختصار
لو موقعك بيفضل شاشة بيضا ثانيتين أو تلاتة قبل ما يظهر أي حاجة، وانت متأكد إن السيرفر سريع، المشكلة غالبًا مش في السيرفر. المشكلة إن المتصفح وصل لوسم <script> في الـ <head>، ووقف كل حاجة عشان ينزّل الملف ده ويشغّله. ركّز في الكلمة دي: وقف. الصفحة كلها مستنية سطر واحد.
مثال بسيط قبل الكلام التقني
تخيّل مطعم فيه طباخ واحد وطابور زباين. الطباخ بيمشي بالترتيب: يعمل طلب، يسلّمه، يبدأ اللي بعده. كل حاجة ماشية.
فجأة جه طلب معقّد: كيكة محتاجة تتخبز 20 دقيقة. الطباخ وقف الطابور كله، فضل قدام الفرن 20 دقيقة، وكل الزباين اللي وراه استنّوا من غير أي سبب. الأكل السريع كان جاهز، بس محدش أخده لأن الطباخ مشغول بالكيكة.
المتصفح بيعمل نفس الغلطة. الطابور هو محتوى صفحتك (النصوص، الصور، الأزرار). الكيكة هي ملف الجافاسكريبت. أول ما المتصفح يوصله وهو بيقرأ الصفحة، بيوقف يخبز الكيكة، والمستخدم بيبصّ في شاشة فاضية.
ليه ده بيحصل علميًا
المتصفح بيقرأ ملف الـ HTML من فوق لتحت، سطر سطر، وبيبني منه شجرة اسمها DOM. العملية دي اسمها parsing (التحليل).
لما الـ parser يوصل لوسم <script> عادي (من غير أي خاصية)، بيحصل تلات حاجات بالترتيب:
- الـ parser بيقف تمامًا عن قراءة باقي الصفحة.
- المتصفح ينزّل ملف السكربت من الشبكة (ممكن ياخد وقت طويل على موبايل بشبكة ضعيفة).
- المتصفح يشغّل السكربت، وبعدها بس يكمّل قراءة الصفحة.
السبب إن السكربت القديم كان يقدر يكتب في الصفحة وهي بتتبني عن طريق document.write، فالمتصفح بيلعب على المضمون ويوقف كل حاجة لحد ما السكربت يخلّص. النتيجة: المستخدم مبيشوفش أي محتوى قبل ما السكربت ينزل ويشتغل. المصطلح الرسمي للمورد اللي بيعمل كده اسمه render-blocking resource، أي مورد يحجب الرسم.
الحل: خاصيتين اسمهم defer و async
الحل إنك تقول للمتصفح: "نزّل السكربت وانت ماشي، بس متوقفش الصفحة عشانه". وده بيتعمل بإضافة كلمة واحدة على الوسم.
<!-- الطريقة الشائعة الغلط: بيوقف رسم الصفحة -->
<head>
<script src="/js/app.js"></script>
</head>
<!-- الأفضل: defer — نزّل بالتوازي، وشغّل بعد ما الصفحة تتبني -->
<head>
<script defer src="/js/app.js"></script>
</head>كلمة defer بتقول للمتصفح: نزّل الملف في الخلفية أثناء قراءة الصفحة، وما تشغّلوش إلا بعد ما تخلص بناء الصفحة كلها. يعني الطباخ سلّم كل الطلبات السريعة الأول، وخبز الكيكة في الآخر. المستخدم شاف المحتوى بدري.
سيناريو واقعي بالأرقام
افترض إن عندك متجر إلكتروني بـ 24 ألف زيارة في اليوم، وملف app.js حجمه 180 كيلوبايت وموجود في الـ <head> من غير أي خاصية. على موبايل متوسط بشبكة بطيئة، أول رسم للمحتوى (First Contentful Paint) بياخد حوالي 2.6 ثانية، لأن المتصفح مستني السكربت ينزل ويشتغل قبل ما يوري أي كلمة.
ضيف defer بس على نفس الوسم. المتصفح دلوقتي بيرسم النص والصور فورًا وينزّل السكربت بالتوازي، فأول رسم ينزل لحوالي 0.9 ثانية. الفرق ~1.7 ثانية، أي تحسّن حوالي 65% في أول رسم، بتغيير كلمة واحدة. الأرقام دي تقديرية وبتختلف حسب حجم السكربت وسرعة الجهاز، لكن الاتجاه ثابت: نقل السكربت من "حاجب" لـ "غير حاجب" بيقصّر الشاشة البيضا.
تقدر تقيسها بنفسك من غير أدوات معقّدة:
// حط ده في آخر صفحتك وشوف الرقم في الـ Console
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.name === 'first-contentful-paint') {
console.log('FCP =', Math.round(entry.startTime), 'ms');
}
}
}).observe({ type: 'paint', buffered: true });الفرق بين defer و async
الاتنين بينزّلوا السكربت بالتوازي من غير ما يوقفوا قراءة الصفحة، لكن بيختلفوا في إمتى السكربت بيشتغل:
defer: بيستنى لحد ما الصفحة كلها تتبني، وبيشغّل السكربتات بالترتيب اللي كتبتها بيه. ده اللي عايزه في 90% من الحالات، خصوصًا لو السكربت بيلمس عناصر في الصفحة أو بيعتمد على سكربت تاني.async: بيشغّل السكربت أول ما ينزل، من غير انتظار وبدون ترتيب مضمون. مناسب للسكربتات المستقلة تمامًا اللي مش بتعتمد على حاجة، زي كود التحليلات (analytics).
الافتراض هنا: إن سكربتاتك متربطة ببعض أو بتشتغل على محتوى الصفحة. لو كده، defer هو الاختيار الآمن. async بيبقى أسرع نظريًا لكنه ممكن يكسر الترتيب لو عندك أكتر من ملف بيعتمدوا على بعض.
الـ trade-off وما يجب الانتباه له
مفيش تحسين ببلاش. الثمن هنا: مع defer، الكود بتاعك بيشتغل متأخر شوية عن الأول. لو فيه سكربت لازم يشتغل قبل ما المستخدم يشوف الصفحة (نادر جدًا، زي كود بيمنع وميض المظهر الليلي)، تأجيله ممكن يعمل وميضة بسيطة. الحل إن السكربتات دي بس تفضل صغيرة و inline في الـ head، والباقي كله defer.
نقطة تانية: async مش بيضمن ترتيب. لو حطّيته على ملفات بتعتمد على بعض، هتلاقي أخطاء "غير معرّف" بتظهر عشوائيًا. لو مش متأكد، استخدم defer.
متى لا تستخدم هذه الطريقة
مش كل سكربت محتاج الكلام ده:
- لو السكربت inline (مكتوب جوّه الوسم مش ملف خارجي)، خاصيتي
deferوasyncبيتجاهلهم المتصفح أصلًا. الحل تنقل الكود لملف خارجي أو تحطه آخر الصفحة. - لو بتستخدم
type="module"، السكربت بيبقى مؤجّل تلقائيًا بطبيعته، فمش محتاج تضيفdefer. - لو عندك سكربت واحد صغير جدًا (أقل من بضع كيلوبايت) في آخر الصفحة قبل
</body>، الفرق هيكون ضئيل ومش محتاج تتعب نفسك.
الخطوة التالية
افتح ملف الـ HTML الرئيسي لموقعك، ودوّر على أي <script src=...> في الـ <head> من غير خاصية. ضيف كلمة defer عليه، احفظ، وشغّل كود قياس الـ FCP اللي فوق قبل وبعد. لو الرقم نزل، يبقى السكربت كان بيحجب الرسم فعلًا. لو عندك أكتر من ملف، خلّيهم كلهم defer عشان الترتيب يفضل مضبوط.
المصادر
- MDN Web Docs — وسم <script> وخصائص defer و async (مصدر تعريف الخصائص وسلوكها).
- web.dev — Eliminate render-blocking resources (مفهوم الموارد الحاجبة للرسم).
- web.dev — First Contentful Paint (FCP) (تعريف المقياس المستخدم في القياس).
- WHATWG HTML Standard — تعريف خاصيتي defer و async رسميًا (السلوك المعتمد في المتصفحات).