مقدمة: لماذا يعتبر أداء JavaScript مهمًا؟
\nأصبح JavaScript (خاصة في بيئة Node.js) أساس تطوير الويب الحديث. سرعة تنفيذ الكود لا تؤثر فقط على تجربة المستخدم، بل أيضًا على تحميل الخادم. التطبيقات البطيئة تفقد الجمهور، والطلبات غير المحسّنة قد تؤدي إلى زيادة مكلفة في القدرات. في هذه المقالة، سنستعرض طرقًا أساسية لكنها فعالة لتحسين الأداء، تناسب حتى المبتدئين.
\nسننظر في ثلاثة مجالات رئيسية: البرمجة غير المتزامنة، إدارة الذاكرة، والتنميط. جميع الأمثلة ستكون على Node.js.
\n\n1. البرمجة غير المتزامنة: تجنب حظر Event Loop
\nيعمل Node.js في وضع أحادي الخيط باستخدام Event Loop. إذا قمت بتنفيذ عملية ثقيلة متزامنة (مثل قراءة ملف كبير أو حلقة معقدة)، فإن الخادم بأكمله "يتجمد" حتى اكتمالها. لتجنب ذلك، استخدم الطرق غير المتزامنة.
\nمثال سيء (كود حظر):
\nconst fs = require('fs');\nconst data = fs.readFileSync('/bigfile.txt', 'utf8'); // يحظر التدفق\nconsole.log('تم قراءة الملف');\n\nمثال جيد (غير حظر):
\nconst fs = require('fs').promises;\nasync function readFile() {\n const data = await fs.readFile('/bigfile.txt', 'utf8');\n console.log('تم قراءة الملف');\n}\nreadFile();\n\nاستخدم async/await والوعود (promises) لجميع عمليات الإدخال/الإخراج (I/O). هذا يسمح لـ Event Loop بمعالجة الطلبات الأخرى أثناء انتظار الرد من قاعدة البيانات أو نظام الملفات.
نصيحة: تجنب الطرق المتزامنة في بيئة الإنتاج
\nطرق مثل fs.readFileSync أو crypto.pbkdf2Sync مسموح بها فقط عند بدء التطبيق. في وقت التشغيل، استخدم إصداراتها غير المتزامنة.
2. تحسين إدارة الذاكرة ومنع التسريبات
\nتسريبات الذاكرة هي أحد الأسباب الشائعة لانخفاض أداء تطبيقات Node.js. تحدث عندما تبقى الكائنات في الذاكرة على الرغم من عدم الحاجة إليها.
\nالأسباب الرئيسية للتسريبات:
\n- \n
- المتغيرات العامة \n
- المؤقتات غير المنظفة (setInterval بدون clearInterval) \n
- المراجع المغلقة في الإغلاقات (closures) \n
- مستمعو الأحداث الذين لا يتم إزالتهم \n
مثال على تسريب عبر المستمعين:
\nconst EventEmitter = require('events');\nconst emitter = new EventEmitter();\n\nfunction processRequest() {\n emitter.on('data', (chunk) => {\n // معالجة البيانات\n });\n}\n// كل استدعاء لـ processRequest يضيف مستمعًا جديدًا، لكنه لا يزيل القديم!\n\nالحل: قم بإزالة المستمعين أو استخدم once
\nemitter.once('data', (chunk) => {\n // سيتم تنفيذه مرة واحدة فقط وسيتم إزالته تلقائيًا\n});\n\nمن المفيد أيضًا استخدام أدوات مثل heapdump لتحليل الذاكرة. لكن في البداية، يكفي مراقبة عدم بقاء المراجع للكائنات في النطاق العام.
3. التخزين المؤقت والتنميط
\nيسمح التخزين المؤقت بتجنب إعادة تنفيذ العمليات الحسابية الثقيلة. يساعد التنميط في العثور على نقاط الضعف في الكود.
\nتخزين مؤقت بسيط باستخدام Map:
\nconst cache = new Map();\n\nfunction expensiveCalculation(input) {\n if (cache.has(input)) {\n console.log('من التخزين المؤقت');\n return cache.get(input);\n }\n const result = input * 1000 + Math.random(); // محاكاة منطق معقد\n cache.set(input, result);\n return result;\n}\n\nconsole.log(expensiveCalculation(5)); // حساب\nconsole.log(expensiveCalculation(5)); // من التخزين المؤقت\n\nللسيناريوهات الأكثر تعقيدًا، استخدم مكتبات مثل node-cache أو Redis.
التنميط باستخدام الأداة المدمجة في Node.js
\nقم بتشغيل التطبيق مع العلم --prof:
node --prof app.js\n\nبعد إيقاف التطبيق، سيظهر ملف isolate-*.log في المجلد. قم بتحويله إلى