بعد شهر من انضمام ثلاثة مطورين جدد إلى فريق خدمة الطلبات في شركة ناشئة تبيع القهوة والتمور، واحد من Java وواحد من Python وواحد من C#، جمع قائد الفريق ملاحظات مراجعة الكود Code Review من ثلاثين طلب دمج، فوجد أن نفس الأخطاء تتكرر بأشكال مختلفة: قائمة أسعار مرتبة بطريقة غريبة، وسكربت يحفظ صفراً من الطلبات ثم يطبع «تم»، ومنتج نفد يظهر «متوفراً»، وتقرير أكتوبر فيه طلبات دفعت في نوفمبر، واسم منتج مقطوع يظهر في الفاتورة برمز غريب. ولم يكن أي منهم مبتدئاً، فكلهم يبرمجون منذ سنوات، والمشكلة أن كل خطأ منها كود صحيح تماماً في لغتهم الأصلية، أو كود يبدو صحيحاً في JavaScript ويعمل بلا أي رسالة خطأ، ويعطي نتيجة خاطئة.
وهذا المقال هو الخامس والأخير في مرحلة JavaScript وTypeScript، وفيه خمسة عشر خطأ من هذا النوع، كل خطأ بتشغيل حقيقي يبين النتيجة الخاطئة، ثم الإصلاح بالأسلوب الحديث للغة، والسبب في سطرين أو ثلاثة. ثم نسأل سؤالاً عملياً: كم من هذه الأخطاء يمسكه TypeScript وكم يمسكه Biome قبل التشغيل؟ وفي النهاية خطة قراءة من المصادر الرسمية لمن يريد أن يكمل بعد هذه المرحلة. وكل الأخطاء في ملف واحد mistakes.mjs شغلناه على Node.js 26 داخل Container، والمخرج منقول كما ظهر.
وسوف نناقش في هذا المقال ما يلي:
- أخطاء الوعود الأربعة:
forEachوifوfilterمع دوال غير متزامنة، وawaitداخل حلقة لطلبات مستقلة. - أخطاء المصفوفات الخمسة: الترتيب كنصوص، والترتيب الذي يغير الأصل، و
map(parseInt)، وfor...in، وfill([]). - أخطاء النطاق والمرجع:
thisالذي يضيع، وvarفي الحلقات، والمقارنة بين كائنين. - التواريخ والنصوص: الشهر الذي يبدأ من صفر، والتاريخ بتوقيت السيرفر، والنص العربي مع رمز تعبيري.
- خطأ أدوات:
npx tscالذي ثبت حزمة لم يطلبها أحد. - من يمسك ماذا: TypeScript وBiome والاختبارات، بالأرقام.
- الأنماط الحديثة التي تستبدل القديمة، ثم خطة قراءة لأربعة أسابيع من المصادر الرسمية.
لماذا تتكرر نفس الأخطاء Mistakes عند كل القادمين؟
JavaScript لغة صممت في 1995 في أيام قليلة لتحريك الصفحات، ثم صارت لغة الويب كله، والقاعدة الأولى في تطويرها منذ ذلك الوقت هي «لا تكسر الويب»، أي أن أي سلوك قديم يجب أن يبقى كما هو مهما كان غريباً، لأن ملايين الصفحات تعتمد عليه. ولذلك لا تصحح اللغة أخطاءها القديمة، وإنما تضيف بجانبها طريقة جديدة صحيحة: let بجانب var، و=== بجانب ==، وtoSorted بجانب sort، وTemporal بجانب Date. وهذا يعني أن الطريقة القديمة الخاطئة ما زالت تعمل، وما زالت في أغلب الأمثلة على الإنترنت، وما زالت أول ما يقترحه المحرر أحياناً، والمطور القادم من لغة أخرى يكتب ما يبدو طبيعياً فيصادف القديم. وقد شرحنا تاريخ اللغة ومعيارها في مقال JavaScript لمن يعرف لغة أخرى، وشرحنا القيم والتحويل والأخطاء في مقال الأنواع والذاكرة والأخطاء، فلن نكرر منها هنا إلا ما يلزم.
والفرق بين المطور القادم من لغة أخرى والمبتدئ أن الأول يحمل معه عادات صحيحة في لغته، وهذه العادات هي بالضبط ما يوقعه في هذه الأخطاء، فالقادم من Python يتوقع أن for x in list يعطي العناصر، والقادم من Java يتوقع أن الدالة لا تنفصل عن كائنها، والقادم من C# يتوقع أن sort يعرف أن العناصر أرقام. ولذلك رتبنا كل خطأ بحيث تبدأ من الكود الذي تكتبه بحكم العادة، ثم ترى ما تفعله JavaScript به فعلاً، ثم الطريقة التي يكتبها من اعتاد هذه اللغة، وقد ناقشنا كيف تنتقل العادات بين اللغات في مقال البرمجة بعدة لغات Polyglot.
وقد يتساءل البعض: أليس للغة دليل أسلوب رسمي Style Guide مثل PEP 8 في Python أو Effective Go؟ والإجابة أنه لا يوجد، فلجنة TC39 تكتب المعيار ولا تكتب أسلوب الكتابة، وما يقوم مقام الدليل في الواقع ثلاثة أشياء: قواعد أداة التدقيق التي يتفق عليها الفريق، مثل قواعد Biome الموصى بها التي استخدمناها في هذه المرحلة، وأمثلة MDN التي تعكس الأسلوب الحديث، والكود الذي تقرؤه في المكتبات الكبيرة المكتوبة حديثاً. ولذلك فإن «الأسلوب الصحيح» في JavaScript قرار يكتبه الفريق في ملف إعداد ويطبقه في CI، وليس شيئاً تحفظه، وهذا المقال يجمع ما تتفق عليه هذه المصادر في الأخطاء التي تتكرر أكثر من غيرها.
والصورة التالية تجمع الأخطاء الخمسة عشر في خمس عائلات، ورقم كل خطأ هو رقمه في الملف وفي المقال:

وشغلنا الملف كله بالأمر التالي، وسوف ننقل مخرج كل خطأ في قسمه:
docker run --rm -v "$PWD:/lab:ro" node:26.13.0-slim sh /lab/run.shأخطاء الوعود Promises: الكود الذي لا ينتظر
أخطر عائلة هي الأولى، لأن نتيجتها لا تظهر في الاختبار السريع، وإنما في بيانات ناقصة أو منتج بيع وهو غير متوفر. والسبب المشترك بينها أن الدوال القديمة في اللغة لا تعرف شيئاً عن الوعود، فهي تأخذ ما ترجعه الدالة غير المتزامنة، وهو وعد Promise، وتتعامل معه كقيمة عادية. وقد شرحنا الوعود والتزامن بالقياسات في مقال التزامن في JavaScript.
الخطأ 4: forEach مع دالة async
القادم من Java يعرف list.forEach(...) ويستخدمه في كل مكان، فيكتب:
const saved = [];
const save = async (id) => { await sleep(10); saved.push(id); };
[1, 2, 3].forEach(async (id) => { await save(id); });
console.log('wrong: saved right after forEach ->', saved.length);wrong: saved right after forEach -> 0
fixed (for...of + await): saved -> 3
fixed (Promise.all + map): saved -> 3الدالة forEach تستدعي الدالة لكل عنصر وتتجاهل ما ترجعه، فتبدأ ثلاث عمليات حفظ وتنتهي فوراً قبل أن تنتهي أي منها، والسطر التالي يرى صفراً. والأسوأ أن أي خطأ في عملية حفظ يصبح وعداً مرفوضاً بلا معالج، فيسقط العملية كلها كما رأينا في مقال الأخطاء. والإصلاح بحسب ما تريد: for...of مع await إذا كان الترتيب مهماً أو أردت عملية واحدة في كل لحظة، وawait Promise.all(items.map(save)) إذا كانت العمليات مستقلة وتريدها معاً. والقاعدة: لا تمرر دالة async إلى forEach أبداً.
الخطأ 5: شرط بدون await
const isInStock = async (id) => { await sleep(1); return id !== 3; };
if (isInStock(3)) console.log('wrong: product 3 is "in stock" because a Promise object is truthy:', isInStock(3) instanceof Promise);
if (!(await isInStock(3))) console.log('fixed: product 3 is out of stock');wrong: product 3 is "in stock" because a Promise object is truthy: true
fixed: product 3 is out of stockهذا السطر من الخدمة الحقيقية تقريباً: الدالة كانت متزامنة تقرأ من الذاكرة، ثم غيرها أحدهم لتسأل قاعدة البيانات، فصارت async، وكل مكان يستدعيها في شرط ما زال يعمل بلا خطأ، ولكن الشرط صار صحيحاً دائماً، لأن الوعد كائن، وكل كائن في JavaScript قيمة «صحيحة» Truthy. فالمنتج الذي نفد صار متوفراً في كل الصفحات. والإصلاح كلمة واحدة await، وهنا الخبر الجيد الوحيد في هذه العائلة: TypeScript يمسك هذا الخطأ تحديداً كما سوف ترى.
الخطأ 6: filter مع دالة async
const isInStock = async (id) => id !== 3;
console.log('wrong:', [1, 2, 3].filter(async (id) => isInStock(id)));
const flags = await Promise.all([1, 2, 3].map(isInStock));
console.log('fixed:', [1, 2, 3].filter((_, i) => flags[i]));wrong: [ 1, 2, 3 ]
fixed: [ 1, 2 ]نفس الخطأ السابق في ثوب آخر: filter تسأل الدالة «هل أبقي هذا العنصر؟»، والدالة ترجع وعداً، والوعد صحيح، فبقيت كل العناصر بما فيها المنتج 3. والإصلاح أن تحسب كل الإجابات أولاً بـ Promise.all، ثم تصفي بالنتائج الجاهزة. ونفس الشيء ينطبق على some وevery وfind وsort، فكل دالة في المصفوفات تأخذ دالة وتستخدم نتيجتها مباشرة لا تعمل مع async.

الخطأ 15: await داخل حلقة لطلبات مستقلة
const fetchPrice = async (id) => { await sleep(50); return id * 100; };
let t0 = performance.now();
const seq = [];
for (const id of [1, 2, 3, 4, 5]) seq.push(await fetchPrice(id));
console.log(`wrong: ${seq} in ${Math.round(performance.now() - t0)} ms`);
t0 = performance.now();
const par = await Promise.all([1, 2, 3, 4, 5].map(fetchPrice));
console.log(`fixed: ${par} in ${Math.round(performance.now() - t0)} ms`);wrong: 100,200,300,400,500 in 252 ms
fixed: 100,200,300,400,500 in 50 msوهذا عكس الخطأ الرابع: هنا الكود ينتظر أكثر مما يجب. فالنتيجة صحيحة في الحالتين، ولكن الحلقة انتظرت كل سعر حتى وصل قبل أن تطلب التالي، فأخذت خمسة أضعاف الوقت، مع أن الطلبات الخمسة مستقلة تماماً. والمطور القادم من كود متزامن يكتب الحلقة بهذا الشكل لأنه الشكل الطبيعي في لغته، وقد قسنا نفس الخطأ بمئة طلب HTTP في مقال التزامن فكان 5.6 ثانية مقابل أقل من 0.3. والقاعدة: إذا كانت العمليات مستقلة فابدأها كلها ثم انتظرها معاً، مع حد للعدد إذا كانت كثيرة، وإذا كان الترتيب مهماً أو كانت كل خطوة تعتمد على التي قبلها فالحلقة مع await هي الصحيحة. وأداة التدقيق لا تستطيع أن تفرق بين الحالتين، لأنها لا تعرف هل العمليات مستقلة أم لا، فهذا قرار تتخذه أنت عند كل حلقة.
كيف تلاحظ أخطاء الوعود في الإنتاج؟ Symptoms
أخطاء هذه العائلة لا تطبع رسالة خطأ في أغلب الحالات، ولذلك تحتاج أن تعرف أعراضها. فإذا رأيت في السجلات سطر «انتهى» قبل أسطر العمل نفسه، أو عدداً أقل من المتوقع في تقرير مهمة ليلية، أو مهمة تنتهي أسرع بكثير مما يجب، فابحث عن forEach أو map بدون Promise.all. وإذا رأيت شرطاً لا يفشل أبداً، مثل منتج متوفر دائماً أو صلاحية ممنوحة دائماً، فابحث عن دالة صارت async مؤخراً وما زال من يستدعيها لا ينتظرها. وإذا سقطت العملية فجأة برسالة خطأ من مكان لا يبدو أن أحداً استدعاه، فهذا وعد مرفوض بلا معالج، كما شرحنا في مقال الأخطاء. وأفضل ما تفعله في الخدمة نفسها أن تسجل كل وعد مرفوض بلا معالج بمعالج للحدث unhandledRejection قبل أن تسقط العملية، حتى يظهر المكان في السجلات، وأن يكون في الاختبارات حالة تتأكد من عدد العناصر المحفوظة بعد المهمة وليس فقط من عدم وجود خطأ. وأما الخطأ الخامس عشر فعرضه الوحيد هو البطء، وتراه في زمن الطلب عندما يكون أكبر من مجموع أزمنة الخدمات التي يسألها بدلاً من أكبرها.
أخطاء المصفوفات Arrays: دوال تعمل ولكن ليس كما تتوقع
دوال المصفوفات في JavaScript قريبة جداً من Streams في Java وLINQ في C# وList Comprehensions في Python، ولذلك يستخدمها القادمون بثقة، ولكن بعض أقدمها له سلوك صمم في التسعينيات ولا يمكن تغييره.
الخطأ 1: sort() للأرقام بدون دالة مقارنة
const prices = [990, 4500, 3800, 100];
console.log('wrong:', [...prices].sort());
console.log('fixed:', [...prices].sort((a, b) => a - b));wrong: [ 100, 3800, 4500, 990 ]
fixed: [ 100, 990, 3800, 4500 ]الهيل بـ 990 هللة جاء في آخر القائمة بعد القهوة بـ 4500، لأن sort بدون دالة مقارنة يحول كل عنصر إلى نص ويرتب النصوص حرفاً حرفاً، كما يذكر توثيق sort في MDN صراحة، و"990" يأتي بعد "4500" لأن 9 أكبر من 4. وفي Python وJava يرتب نفس السطر الأرقام كأرقام، ولذلك لا يخطر للقادمين أن يتحققوا. والقاعدة: مرر دالة مقارنة دائماً، (a, b) => a - b للأرقام تصاعدياً، وa.localeCompare(b, 'ar') للنصوص العربية.
وأين يظهر هذا في خدمة حقيقية؟ غالباً في نقطة تعرض المنتجات مرتبة بالسعر، عندما يرتبها الكود في JavaScript بعد قراءتها بدلاً من ORDER BY price_halalas في الاستعلام، فيعمل كل شيء في الاختبار لأن الأسعار فيه بنفس عدد الخانات، ثم يظهر الخطأ عندما يضاف أول منتج بسعر من خانة أقل. ولهذا السبب الثاني نفضل أن يرتب PostgreSQL ما يستطيع ترتيبه، فهو يرتب الأرقام كأرقام دائماً، ويستخدم الفهرس.
الخطأ 2: sort() يغير المصفوفة الأصلية
const shown = [990, 4500, 3800];
const cheapestFirst = shown.sort((a, b) => a - b);
console.log('wrong: original after sort ->', shown, '| same array?', cheapestFirst === shown);
const shown2 = [990, 4500, 3800];
const sorted2 = shown2.toSorted((a, b) => a - b);
console.log('fixed: original after toSorted ->', shown2, '| sorted ->', sorted2);wrong: original after sort -> [ 990, 3800, 4500 ] | same array? true
fixed: original after toSorted -> [ 990, 4500, 3800 ] | sorted -> [ 990, 3800, 4500 ]المطور كتب const cheapestFirst = shown.sort(...) وكأنه يحصل على نسخة مرتبة، فتغير ترتيب القائمة الأصلية التي تعرض في مكان آخر، والسطر same array? true يقول إن المتغيرين يشيران إلى نفس المصفوفة. وsort وreverse وsplice كلها تغير المصفوفة في مكانها In-place، ومنذ ES2023 توجد نسخ لا تغير الأصل: toSorted وtoReversed وtoSpliced وwith. والقاعدة في الكود الحديث: استخدم النسخ التي لا تغير الأصل، إلا إذا كنت تملك المصفوفة وتقصد تغييرها.
الخطأ 3: map(parseInt)
wrong: [ 1, NaN, NaN, 3 ]
fixed: [ 1, 2, 3, 10 ] [ 1, 2, 3, 10 ]الكمية "10" صارت 3، و"2" و"3" صارتا NaN. والسبب أن map تمرر للدالة ثلاثة معاملات: العنصر ورقمه والمصفوفة، وparseInt تأخذ معاملاً ثانياً هو أساس العد Radix، فصار الاستدعاء parseInt("2", 1) وparseInt("3", 2) وparseInt("10", 3)، والأخير يقرأ 10 بالنظام الثلاثي فيعطي 3. والإصلاح map(Number) لأن Number تتجاهل المعاملات الإضافية، أو دالة صريحة (s) => Number.parseInt(s, 10). والدرس الأعم: لا تمرر دالة لم تكتبها إلى map مباشرة إلا إذا كنت تعرف كل معاملاتها.
والمكان الذي يظهر فيه هذا الخطأ عادة هو قراءة قائمة معرفات من رابط مثل ?ids=3,1,10، فيكتب المطور ids.split(',').map(parseInt)، فيصبح المعرف الثاني NaN والثالث رقماً آخر تماماً، ويرجع الاستعلام منتجات غير التي طلبها العميل دون أي خطأ. ولاحظ أن التحقق بمخطط عند الحدود كما فعلنا في خدمة الطلبات كان سيمنع NaN من الوصول إلى الاستعلام، ولكنه لن يكشف أن 10 صارت 3، لأن 3 معرف صحيح.
الخطأ 9: for...in على مصفوفة
const qty = [3, 1, 2];
const wrong = [];
for (const i in qty) wrong.push(i + 1);
console.log('wrong: line numbers ->', wrong, typeof wrong[0]);
const fixed = [];
for (const [i, q] of qty.entries()) fixed.push(`${i + 1}:${q}`);
console.log('fixed:', fixed);wrong: line numbers -> [ '01', '11', '21' ] string
fixed: [ '1:3', '2:1', '3:2' ]القادم من Python يكتب for i in qty فيتوقع العناصر، والقادم من C# يتوقع الأرقام، وfor...in في JavaScript يعطي أسماء الخصائص كنصوص، كما يشرح توثيق for...in، فـ i + 1 صار دمج نصوص: "0" + 1 = "01". وهي مصممة للكائنات وليس للمصفوفات، وقد تمر أيضاً على خصائص موروثة. والقاعدة: for...of للعناصر، و.entries() عندما تحتاج الرقم والعنصر معاً.
والخطأ يظهر عادة في ترقيم بنود الفاتورة، فتطبع البنود بأرقام 01 و11 و21 بدلاً من 1 و2 و3، أو في جمع الكميات حيث يصبح المجموع نصاً طويلاً من الأرقام الملتصقة بدلاً من رقم. وفي TypeScript يكون نوع المتغير في for...in نصاً string، فلو استخدمته كرقم في دالة تنتظر number لاعترض المترجم، ولكن i + 1 عملية مسموحة على النصوص، فتمر.
الخطأ 10: new Array(n).fill([])
wrong: [["gift"],["gift"],["gift"]]
fixed: [["gift"],[],[]]أراد المطور ثلاث قوائم فارغة لثلاثة طلبات، فأضاف «تغليف هدية» للأول فظهر في الثلاثة. والسبب أن fill تضع نفس القيمة في كل خانة، والقيمة هنا مصفوفة واحدة، فالخانات الثلاث تشير إليها، وهذا نفس درس النسخ بالمرجع في مقال الأنواع. والإصلاح Array.from({ length: 3 }, () => [])، لأن الدالة تستدعى مرة لكل خانة فتنشئ مصفوفة جديدة. والقادم من Python يعرف هذا الفخ من [[]] * 3، ولكنه لا يتوقعه هنا.
ونفس المشكلة تظهر بأشكال أخرى أقل وضوحاً، مثل قيمة افتراضية لكائن إعدادات معرف خارج الدالة ثم يعدل داخلها، أو كائن واحد يعاد استخدامه كقالب لكل الطلبات، فيعدل طلب ما الحقل items في القالب فيظهر في الطلب التالي. والقاعدة التي تمنع العائلة كلها: كل كائن أو مصفوفة تريد أن تكون مستقلة يجب أن تنشأ بنفسها، بدالة تستدعى في كل مرة، وليس بقيمة واحدة تنسخ مرجعها.

لماذا لا تصلح اللغة sort القديمة؟ التوافق مع الويب Web Compatibility
قد يبدو الحل البسيط أن تجعل اللجنة sort ترتب الأرقام كأرقام، كما في كل اللغات الأخرى، وينتهي الأمر. والسبب في أن هذا لن يحدث أبداً قصة موثقة حدثت في 2018، يحكيها فريق Chrome في تدوينة SmooshGate: كانت اللجنة تضيف دالة جديدة للمصفوفات اسمها flatten، فلما شحنتها Firefox في نسخة تجريبية تعطلت مواقع حقيقية، لأن مكتبة MooTools القديمة المنتشرة في مواقع كثيرة كانت تعرف دالة بنفس الاسم بسلوك مختلف، والإضافة الرسمية غيرت سلوكها. فاضطرت اللجنة إلى تغيير الاسم إلى flat، بعد أن اقترح صاحب المقترح على سبيل المزاح اسم smoosh. فإذا كانت إضافة اسم جديد قد تكسر مواقع، فتغيير سلوك sort الذي تعتمد عليه ملايين الصفحات منذ ثلاثين سنة مستحيل. ولهذا السبب جاءت toSorted كاسم جديد بدلاً من إصلاح القديم، وجاءت Temporal بدلاً من إصلاح Date، وهذا هو المنطق الذي يجب أن تفهمه لتتوقع أين ستجد البديل الحديث: اسم جديد بجانب القديم دائماً.
أخطاء النطاق والمرجع Scope and Identity
الخطأ 7: this يضيع عندما تمرر الدالة
class Cart {
items = [990, 3800];
total() { return this.items.reduce((a, b) => a + b, 0); }
}
const cart = new Cart();
const run = (fn) => { try { return fn(); } catch (e) { return `${e.constructor.name}: ${e.message}`; } };
console.log('wrong:', run(cart.total));
console.log('fixed:', run(() => cart.total()), run(cart.total.bind(cart)));wrong: TypeError: Cannot read properties of undefined (reading 'items')
fixed: 4790 4790في Java وC# تبقى الدالة مرتبطة بكائنها دائماً، أما في JavaScript فقيمة this تحدد عند الاستدعاء وليس عند الكتابة، كما يشرح توثيق this. فـ cart.total() تستدعيها مع this = cart، أما run(cart.total) فيمرر الدالة وحدها، وعندما تستدعى بلا كائن تكون this قيمتها undefined في الوضع الصارم. وهذا يظهر كثيراً مع setTimeout ومعالجات الأحداث ومسارات الإطارات. والإصلاح إما دالة سهمية تستدعيها بكائنها، وإما bind، وإما أن تعرف الدالة في الكلاس كحقل سهمي total = () => ... فتأخذ this من الكائن دائماً.
الخطأ 8: var داخل حلقة مع دوال
wrong: [ 3, 3, 3 ] | fixed: [ 0, 1, 2 ]ثلاث دوال أنشئت في حلقة، وكل واحدة يجب أن ترجع رقم دورتها، ولكنها كلها رجعت 3. والسبب أن var لها نطاق الدالة كلها وليس نطاق الحلقة، فهناك متغير i واحد تشترك فيه الدوال الثلاث، وقيمته بعد انتهاء الحلقة 3. أما let فتنشئ متغيراً جديداً لكل دورة. ولا يوجد سبب لكتابة var في كود جديد، والقاعدة التي شرحناها في المقال الأول: const افتراضياً، وlet عندما تعيد الإسناد.
وقد يتساءل البعض: من يكتب var في 2026؟ والإجابة أن المطور لا يكتبها في الغالب، وإنما ينسخها من مثال قديم أو من إجابة في منتدى، أو يجدها في ملف قديم في المشروع فيضيف إليه بنفس الأسلوب. ولاحظ أن Biome أمسك هذا الخطأ تحديداً في التجربة لأن var كانت داخل كتلة، وهذا سبب إضافي لأن تكون أداة التدقيق جزءاً من CI وليس اختيارية: هي التي تمنع الأسلوب القديم من الدخول مع كل نسخ ولصق.
الخطأ 11: المقارنة بين كائنين بالهوية Identity
wrong: false 2 false
fixed: unique by key -> 1 trueكائنان بنفس المحتوى { product_id: 3 } ليسا متساويين بـ ===، وSet تعتبرهما عنصرين، وincludes لا تجد كائناً جديداً بنفس المحتوى. فالمقارنة في JavaScript بين الكائنات بالهوية، أي «هل هما نفس الكائن في الذاكرة؟»، وليس بالمحتوى، ولا يوجد ما يقابل equals وhashCode في Java أو __eq__ في Python. والإصلاح أن تقارن بمفتاح: Map بمفتاح product_id لإزالة التكرار، وsome((x) => x.product_id === 3) للبحث. وهذا بالضبط ما فعلته خدمة الطلبات عندما جمعت الكميات بـ Map قبل القفل.
أين يظهر this والهوية في كود حقيقي؟
في كود الواجهات القديم كان this مصدر أكثر الأسئلة تكراراً، لأن معالجات الأحداث تستدعى من المتصفح وليس من كائنك، أما في كود السيرفر الحديث فهو أقل ظهوراً، لأن أغلب الكود دوال وليس كلاسات، والدوال السهمية تأخذ this من مكانها. ولكنه يعود عندما تمرر دالة من كلاس خدمة إلى مكتبة، مثل setInterval(this.cleanup, 60000) في كاش يحذف العناصر القديمة، فتعمل الدالة كل دقيقة وترمي TypeError في كل مرة دون أن يلاحظ أحد إلا من السجلات. والمقارنة بالهوية تظهر في كل مكان تستخدم فيه Set أو Map بمفتاح كائن، مثل كاش مفتاحه كائن الطلب نفسه، فلا يجد الكاش أي شيء أبداً لأن كل طلب كائن جديد، ويبدو الكاش كأنه يعمل ولكن نسبة الإصابة صفر. والعلاج في الحالتين واحد: مفاتيح بسيطة من نصوص أو أرقام، ودوال سهمية عند التمرير.
النصوص والتواريخ Text and Dates
الخطأ 12: الشهر من صفر، والتاريخ بتوقيت السيرفر Time Zone
const d = new Date(2026, 10, 1);
console.log('wrong: new Date(2026, 10, 1) ->', d.toDateString());
const paidAt = new Date('2026-10-31T21:30:00Z'); // 00:30 on 1 November in Saudi Arabia
console.log('server TZ:', Intl.DateTimeFormat().resolvedOptions().timeZone);
console.log('wrong: ar-SA in server time ->', paidAt.toLocaleDateString('ar-SA'));
console.log('fixed:', paidAt.toLocaleDateString('ar-SA', { timeZone: 'Asia/Riyadh', dateStyle: 'long' }),
'|', paidAt.toLocaleDateString('ar-SA-u-ca-islamic-umalqura', { timeZone: 'Asia/Riyadh', dateStyle: 'long' }),
'|', new Intl.DateTimeFormat('en-CA', { timeZone: 'Asia/Riyadh' }).format(paidAt));wrong: new Date(2026, 10, 1) -> Sun Nov 01 2026
server TZ: UTC
wrong: ar-SA in server time -> ٣١/١٠/٢٠٢٦
fixed: ١ نوفمبر ٢٠٢٦ | ٢١ جمادى الأولى ١٤٤٨ هـ | 2026-11-01في هذا المخرج خطآن. الأول أن new Date(2026, 10, 1) هو الأول من نوفمبر وليس أكتوبر، لأن الشهور في Date تبدأ من صفر بينما الأيام تبدأ من واحد، وهذا من تصميم 1995 الذي نسخ Java القديمة. والثاني أخطر: عميل دفع الساعة 12:30 بعد منتصف الليل بتوقيت السعودية في الأول من نوفمبر، والسيرفر يعمل بتوقيت UTC كما هو الحال في أغلب الـ Containers، فتنسيق التاريخ بدون منطقة زمنية أعطى 31 أكتوبر، فدخل الطلب في تقرير أكتوبر، وهذه هي الشكوى في القصة. والإصلاح أن تحدد timeZone: 'Asia/Riyadh' في كل تنسيق موجه للعميل، وأن تخزن الوقت دائماً كلحظة بتوقيت UTC، كما يفعل العمود timestamptz في PostgreSQL. ولاحظ أن نفس الدالة تعطي التاريخ الهجري إذا طلبته صراحة بالتقويم islamic-umalqura، وهذا مفيد في الفواتير والتقارير المحلية.
ولاحظ أيضاً أن تنسيق ar-SA يكتب الأرقام بالأرقام الهندية مثل ٣١، وهذا صحيح لغوياً، ولكنه قد لا يكون ما تريده إذا كانت واجهتك تستخدم الأرقام العربية الغربية، أو إذا كان النص سيقرأ لاحقاً بكود يتوقع أرقاماً لاتينية. والحل إضافة نظام الترقيم إلى اسم اللغة، فجربنا ar-SA-u-nu-latn على نفس اللحظة وعلى مبلغ 34.16 ريالاً كعملة:
1 نوفمبر 2026 ٣٤٫١٦ ر.س. 34.16 ر.س.فالتاريخ صار بأرقام غربية مع اسم الشهر العربي، والمبلغ بـ ar-SA وحدها كتب بالأرقام الهندية وبفاصلة عشرية عربية، ومع -u-nu-latn صار 34.16 ر.س.. والقاعدة: قرر شكل الأرقام في الواجهة مرة واحدة، واكتبه في دالة تنسيق واحدة يستخدمها كل الكود، ولا تخزن أبداً المبلغ المنسق كنص، وإنما الهللات كعدد صحيح.
والأفضل في الكود الجديد هو الواجهة الحديثة Temporal، وهي متاحة في Node.js 26 بدون أي مكتبة، وقد جربناها على نفس اللحظة:
const paidAt = Temporal.Instant.from('2026-10-31T21:30:00Z');
const riyadh = paidAt.toZonedDateTimeISO('Asia/Riyadh');
console.log('instant :', paidAt.toString());
console.log('in Riyadh :', riyadh.toString());
console.log('date only :', riyadh.toPlainDate().toString(), '| month:', riyadh.month);
const due = riyadh.toPlainDate().add({ days: 30 });
console.log('due in 30 d :', due.toString(), '| month 11 really is November:', Temporal.PlainDate.from({ year: 2026, month: 11, day: 1 }).toString());instant : 2026-10-31T21:30:00Z
in Riyadh : 2026-11-01T00:30:00+03:00[Asia/Riyadh]
date only : 2026-11-01 | month: 11
due in 30 d : 2026-12-01 | month 11 really is November: 2026-11-01وTemporal تفرق بين أنواع لا يفرق بينها Date: اللحظة Instant، والتاريخ والوقت في منطقة زمنية ZonedDateTime، والتاريخ وحده PlainDate. والشهور فيها من 1 إلى 12، والعمليات مثل «أضف 30 يوماً» ترجع قيمة جديدة ولا تغير الأصل. والقاعدة: Temporal لكل كود تواريخ جديد على Node.js 26، وDate فقط حيث تفرضه مكتبة أو واجهة قديمة.
وأين يظهر خطأ المنطقة الزمنية في خدمة حقيقية؟ في كل تقرير يجمع حسب اليوم أو الشهر، وفي كل حد زمني مثل «العرض ينتهي منتصف الليل»، وفي كل مقارنة بين تاريخ مكتوب في الكود وتاريخ قادم من قاعدة البيانات. والقاعدة التي تمنع أغلبه: خزن اللحظة بتوقيت UTC، وحول إلى منطقة العميل عند العرض فقط، واحسب حدود الأيام والشهور في منطقة العمل الصريحة Asia/Riyadh وليس في منطقة السيرفر، لأن منطقة السيرفر قد تتغير عندما تنقل الخدمة إلى مزود آخر، ولا يتغير الكود.
الخطأ 13: طول النص String Length ودالة slice بوحدات UTF-16
const name = 'تمر 🌴 سكري';
console.log('length:', name.length, '| code points:', [...name].length);
const cut = name.slice(0, 5);
console.log('wrong: slice(0, 5) ->', JSON.stringify(cut), cut.isWellFormed());
const seg = [...new Intl.Segmenter('ar', { granularity: 'grapheme' }).segment(name)].slice(0, 5).map((s) => s.segment).join('');
console.log('fixed: first 5 graphemes ->', JSON.stringify(seg), seg.isWellFormed());length: 11 | code points: 10
wrong: slice(0, 5) -> "تمر \ud83c" false
fixed: first 5 graphemes -> "تمر 🌴" trueاسم المنتج فيه عشرة رموز يراها الإنسان، وlength يقول 11، لأن النصوص في JavaScript مخزنة بترميز UTF-16، وكل حرف عربي وحدة واحدة، أما الرمز التعبيري للنخلة فخارج المستوى الأساسي فيأخذ وحدتين تسمى زوجاً بديلاً Surrogate Pair. وعندما قطع الكود أول خمس وحدات ليعرض اسماً مختصراً في الفاتورة، قطع النخلة من منتصفها، فبقي نصف زوج \ud83c لا معنى له، والدالة isWellFormed() تقول إن النص لم يعد صالحاً. والإصلاح Intl.Segmenter الذي يقسم النص إلى ما يراه الإنسان حرفاً واحداً Grapheme، وهذا مهم أيضاً للنص العربي مع التشكيل أو مع رموز مركبة، فالحرف مع حركته وحدتان ولكنه حرف واحد على الشاشة. والقاعدة: لا تقطع نصاً يراه المستخدم بـ slice أو substring، ولا تحسب حدود الحقول بـ length إذا كان الحد مهماً للمستخدم.
وهذا يهمك أكثر مما يبدو إذا كانت خدمتك ترسل رسائل نصية SMS أو إشعارات بحد للطول، أو تعرض أسماء المنتجات في مساحة محدودة، أو تخزنها في عمود بحد أقصى. فالحد في قاعدة البيانات مثل varchar(50) في PostgreSQL يحسب بالرموز، وlength في JavaScript يحسب بوحدات UTF-16، وحد الرسائل النصية يحسب بطريقة ثالثة بحسب الترميز، وكل اختلاف بينها مكان لنص يقطع في المنتصف. فاختبر هذه الحدود دائماً بنص عربي فيه رمز تعبيري، وليس بنص إنجليزي فقط، لأن النص الإنجليزي لا يكشف أياً من هذه الفروق.

خطأ أدوات Tooling: npx tsc ثبت حزمة غريبة
الخطأ 14: npx في مجلد لا توجد فيه الحزمة
هذا الخطأ حدث لنا فعلاً أثناء كتابة مقال الخدمة الحقيقية، فقد شغلنا npx tsc في Container من مجلد لا يحتوي على المشروع، فبدلاً من رسالة «الأمر غير موجود» ظهر ما يلي:
npm warn exec The following package was not found and will be installed: [email protected]
npm warn deprecated [email protected]: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.
This is not the tsc command you are looking for
To get access to the TypeScript compiler, tsc, from the command line either:
- Use npm install typescript to first add TypeScript to your project before using npx
- Use yarn to avoid accidentally running code from un-installed packagesوالأمر npx يبحث عن الأمر في node_modules/.bin للمشروع، فإذا لم يجده لا يتوقف، وإنما يبحث في سجل npm عن حزمة بنفس الاسم، ويحملها، ويشغلها. والحزمة التي اسمها tsc ليست TypeScript، وإنما حزمة قديمة حجزت الاسم، ولحسن الحظ أن ما تفعله هو أن تطبع هذا التحذير فقط. ولكن تخيل نفس الشيء مع اسم أداة أقل شهرة، أو بخطأ إملائي واحد في الاسم، فتشغل كوداً من شخص لا تعرفه بصلاحياتك على جهازك أو في CI، وهذا هو الأسلوب الذي تستخدمه هجمات انتحال الأسماء Typosquatting في سجلات الحزم. والإصلاح أن تشغل الأدوات من سكربتات package.json بـ npm run، أو أن تستخدم npx --no-install الذي يرفض التحميل، وأن تثبت الأداة في المشروع قبل استخدامها كما شرحنا في المقال الأول.
من يمسك هذه الأخطاء؟ TypeScript أم Biome أم الاختبارات Tests
بعد أن رأيت الأخطاء، السؤال الطبيعي: ألا تكفي الأدوات؟ فكتبنا سبعة من الأخطاء في ملف TypeScript صغير check.ts، وشغلنا عليه tsc --strict، ثم شغلنا Biome بإعداداته الموصى بها على mistakes.mjs كله:
=== TypeScript 7 --strict on check.ts
check.ts(10,7): error TS2801: This condition will always return true since this 'Promise<boolean>' is always defined.
tsc exit 1
=== Biome 2.6.4 lint on mistakes.mjs
biome exit 1
mistakes.mjs:69:8 lint/correctness/noInnerDeclarations
Checked 1 file in 48ms. No fixes applied.
Found 1 error.والنتيجة أقل بكثير مما يتوقعه كثيرون: TypeScript الصارم أمسك خطأ واحداً من سبعة، وهو الشرط بدون await، برسالة واضحة تقول إن الشرط صحيح دائماً لأن الوعد موجود دائماً. وBiome أمسك خطأ واحداً من الأربعة عشر الموجودة في الملف، وهو var داخل كتلة، بالقاعدة noInnerDeclarations. أما sort() للأرقام، وmap(parseInt)، وfilter(async)، وthis الضائع، وfor...in، وincludes لكائن، فكلها كود صحيح من حيث الأنواع، لأن sort مسموح على مصفوفة أرقام، وparseInt تقبل معاملاً ثانياً رقمياً، وfilter تقبل أي دالة ترجع أي شيء.

وهذا لا يعني أن الأدوات بلا فائدة، فهي تمسك عائلات أخرى كاملة كما رأيت في مقال TypeScript، وتوجد قواعد تدقيق إضافية خارج الإعداد الموصى به تمسك بعض هذه الأخطاء، مثل قواعد تمنع الوعود في أماكن لا تنتظرها، ولكنها تحتاج معلومات الأنواع ويجب أن تفعلها بنفسك. والدرس الحقيقي أن هذه الأخطاء كلها تنتج قيمة خاطئة وليس خطأ، والأداة الوحيدة التي تمسك القيمة الخاطئة هي اختبار يقارن النتيجة بقيمة متوقعة، مثل اختبار الضريبة 2970 ثم 446 في مقال الخدمة. فاكتب الاختبار بقيمة محسوبة يدوياً وليس بقيمة نسختها من المخرج، لأن المخرج قد يكون هو الخطأ نفسه.
قواعد التدقيق التي تعرف الأنواع Type-aware Linting
عائلة الوعود بالذات لها حل أقوى من الإعداد الموصى به، وهو قواعد تدقيق تقرأ الأنواع من مترجم TypeScript، فتعرف أن الدالة التي مررتها إلى forEach ترجع وعداً، وأن الشرط في if وعد، وأن استدعاء دالة async بقي بلا await. وأشهرها في مشروع typescript-eslint قاعدتا no-floating-promises وno-misused-promises، وهما تمسكان الأخطاء الرابع والخامس والسادس كلها من حيث المبدأ. وثمنها أن التدقيق يصبح أبطأ بكثير لأنه يحتاج أن يبني صورة الأنواع للمشروع كله، وأنها تعتمد على واجهة برمجية للمترجم، فتأكد من توافقها مع إصدار TypeScript الذي تستخدمه قبل أن تعتمد عليها، خاصة مع المترجم الأصلي الجديد في الإصدار 7. ولم نشغلها في هذه التجربة، لذلك لا نضعها في الجدول، والجدول يبين ما أمسكته الأدوات فعلاً بإعداداتها الافتراضية. والقرار العملي لفريق كبير يكتب كثيراً من الكود غير المتزامن أن يجربها على المشروع، ويقيس كم تضيف إلى زمن CI، ثم يقرر.
الأنماط الحديثة Modern Patterns التي تستبدل القديمة
كل خطأ في هذا المقال له بديل حديث، ومن المفيد أن تجمعها في قائمة واحدة تراجعها في كل مراجعة كود، وكلها متاحة في Node.js 26 بدون أي مكتبة، وتحققنا من وجودها في نفس الـ Container:
| بدلاً من | استخدم | السبب |
|---|---|---|
var | const وlet | نطاق الكتلة، ومتغير جديد لكل دورة |
== | === وObject.is | بدون تحويل تلقائي |
|| للقيمة الافتراضية | ?? و?. | الصفر والنص الفارغ قيم صحيحة |
sort وreverse وsplice | toSorted وtoReversed وtoSpliced | لا تغير الأصل |
JSON.parse(JSON.stringify(x)) | structuredClone(x) | نسخ عميق يحفظ التواريخ وMap |
for...in على المصفوفات | for...of و.entries() | العناصر وليس أسماء الخصائص |
| ردود Callbacks متداخلة | async/await وPromise.all | تسلسل مقروء وأخطاء تمر إلى catch |
new Promise يدوي مع متغيرات خارجية | Promise.withResolvers() | الوعد ودالتا الحل معاً |
Date | Temporal | أنواع منفصلة ومناطق زمنية وشهور من 1 |
slice لنص يراه المستخدم | Intl.Segmenter | حدود الحروف كما يراها الإنسان |
| حلقات لاتحاد وتقاطع المجموعات | Set.prototype.union وintersection | عمليات المجموعات مدمجة |
require | import مع "type": "module" | المعيار الرسمي |
وكيف تعرف أن ميزة جديدة متاحة في إصدارك؟ أول مكان هو صفحة الميزة في MDN، ففي أسفل كل صفحة جدول توافق Browser Compatibility فيه سطر لـ Node.js، والمكان الثاني هو قائمة مقترحات TC39 التي تبين في أي مرحلة كل ميزة، فالميزة في المرحلة 4 دخلت المعيار، وما قبلها قد يتغير. والطريقة الأسرع هي أن تجرب في node نفسه، كما فعلنا عندما تحققنا من typeof Temporal قبل أن نكتب عنها.
كيف تتعلم الأسلوب Idioms بنفسك؟ القراءة والتجربة والتصحيح
الأخطاء الخمسة عشر هنا هي الأكثر تكراراً، ولكنها ليست كل شيء، والطريقة التي تجعلك تكتشف الباقي بنفسك قبل أن تصل إلى الإنتاج لها ثلاث عادات. الأولى أن تجرب أي سطر تشك فيه في node مباشرة قبل أن تكتبه في الكود، فالأمر node بدون ملف يفتح سطر أوامر تفاعلياً REPL يقبل حتى await في أعلى السطر، وتجربة [990, 4500].sort() تأخذ ثانيتين وتوفر عليك ساعة تصحيح. والثانية أن تقرأ كود مكتبات مكتوبة حديثاً ومستخدمة بكثرة، مثل كود Fastify الذي استخدمناه في الخدمة، فسوف ترى كيف يكتب أصحاب الخبرة الحلقات والأخطاء والوعود، وأين يستخدمون الكلاسات وأين يكتفون بالدوال. والثالثة أن تستخدم المصحح Debugger بدلاً من console.log عندما لا تفهم ما يحدث، فالخيار --inspect يجعل Node.js يقبل اتصال أدوات المطور في Chrome أو المحرر، فتضع نقطة توقف وترى قيمة this والمتغيرات في كل سطر، كما يشرح دليل التصحيح في Node.js، وقد شرحنا طريقة التصحيح عموماً في مقال قراءة رسالة الخطأ والتصحيح Debugging.
وعادة رابعة تخص هذه اللغة تحديداً: انتبه إلى تاريخ أي شرح تقرؤه. فالشرح المكتوب قبل 2017 سيستخدم var والردود Callbacks وrequire، والمكتوب قبل 2023 لن يعرف toSorted، والمكتوب قبل 2025 سيقول لك إنك تحتاج ts-node أو أداة بناء لتشغيل TypeScript في Node.js. والكود القديم ما زال يعمل، ولهذا لا يكتشف القارئ أنه قديم، فيتعلم الأسلوب القديم ويكتبه في مشروع جديد. والقاعدة: إذا رأيت var في أول مثال من شرح فابحث عن مصدر أحدث، وارجع دائماً إلى صفحة الميزة في MDN لترى هل لها بديل أحدث، فأغلب صفحات الدوال القديمة تذكر البديل في أولها.
خطة القراءة Reading Plan من المصادر الرسمية
إذا وصلت إلى هنا من المقال الأول في هذه المرحلة فلديك ما يكفي لتعمل في مشروع Node.js مع TypeScript، والخطوة التالية هي أن تقرأ من المصادر الرسمية مباشرة، وليس من شروحات متفرقة قد تكون قديمة. والخطة التالية لأربعة أسابيع بساعة يومياً تقريباً، وكل الروابط لمصادر رسمية:
- الأسبوع الأول، اللغة نفسها: دليل JavaScript في MDN من البداية حتى الوعود وasync، وجرب كل مثال في
nodeوليس في المتصفح فقط، وارجع إلى مرجع JavaScript عند كل دالة لا تعرف معاملاتها. وMDN يكتبه ويراجعه فريق من صناع المتصفحات، وهو أقرب شيء إلى توثيق رسمي سهل القراءة. - الأسبوع الثاني، الأنواع: كتيب TypeScript Handbook، وخاصة الأنواع اليومية والتضييق Narrowing والأنواع المشتقة من أنواع، وجرب في ساحة التجربة TypeScript Playground مع
strict. ثم ارجع إلى مقال TypeScript للمطورين لترى ما تكشفه الأنواع وما لا تكشفه. - الأسبوع الثالث، Node.js: قسم Learn في موقع Node.js، ثم توثيق الواجهات لإصدار 26:
fs/promisesوhttpوtestوworker_threadsوprocess، واقرأ صفحة كل وحدة حتى قسم History الذي يقول متى تغير كل شيء. - الأسبوع الرابع، الإطار والمتابعة: توثيق الإطار الذي يستخدمه فريقك كاملاً، مثل توثيق Fastify الذي استخدمناه في الخدمة، ثم اشترك في مدونة V8 التي تشرح كيف يعمل المحرك ولماذا يكون كود أسرع من آخر، وتابع ما يصل إلى المرحلة 4 في مقترحات TC39.
وما الذي لا تحتاج قراءته الآن؟ تفاصيل المتصفح مثل DOM وCSS إذا كان عملك على السيرفر، وأدوات البناء والتجميع مثل webpack وBabel لأن Node.js 26 لا يحتاجها، والأطر الكاملة مثل NestJS قبل أن تفهم الطبقة التي تحتها. ولا تبدأ بكتب JavaScript الشهيرة المكتوبة قبل ES2015 مهما كانت قيمتها التاريخية، فأغلب صفحاتها تشرح مشكلات حلتها let والكلاسات والوحدات والوعود، وستتعلم منها حلولاً التفافية لم تعد تحتاجها. وإذا كان وقتك محدوداً فالأولوية لما تكتبه كل يوم: الوعود والأخطاء والوحدات والمصفوفات والنصوص، ثم ما يخص عملك.
والمرجع الأخير عند أي خلاف هو معيار ECMAScript نفسه، وهو صعب القراءة في البداية، ولكنه الجواب الوحيد الدقيق عن أسئلة مثل «لماذا [] == false؟»، ومع الوقت سوف تجد أن قراءة خوارزمية واحدة فيه أسرع من قراءة خمس إجابات متناقضة في المنتديات.

قائمة تحقق Checklist لمراجعة الكود
- هل توجد دالة
asyncداخلforEachأوfilterأوsomeأوfind؟ - هل كل استدعاء لدالة
asyncعليهawaitأو يمر إلىPromise.allأو له.catch؟ - هل كل
sortلأرقام فيه دالة مقارنة، وهل يغير مصفوفة لا يملكها؟ - هل تمر دالة لم تكتبها إلى
mapمباشرة؟ - هل يوجد
varأوfor...inعلى مصفوفة أوfillبكائن؟ - هل تمرر دالة من كلاس كـ Callback بدون
bindأو دالة سهمية؟ - هل تقارن كائنات بـ
===أوincludesوأنت تقصد المحتوى؟ - هل كل تنسيق تاريخ للعميل فيه
timeZone، وهل الشهر فيnew Dateمحسوب من صفر؟ - هل يقطع الكود نصاً يراه المستخدم بـ
slice؟ - هل تشغل الأدوات من
npm runوليس منnpxفي مكان قد لا تكون مثبتة فيه؟ - هل لكل حساب مالي اختبار بقيمة محسوبة يدوياً؟
واستخدم القائمة في أول أسابيع المطور الجديد كما هي، ثم اختصرها بعد ذلك إلى ما يتكرر في فريقك فعلاً، فالقائمة الطويلة التي لا يقرؤها أحد أقل فائدة من خمسة بنود يراجعها الجميع في كل طلب دمج، والأفضل من الاثنين أن يتحول كل بند يمكن فحصه آلياً إلى قاعدة في أداة التدقيق أو اختبار في CI، ويبقى في القائمة ما يحتاج عيناً بشرية فقط.
تدريب عملي
- شغل
mistakes.mjsعلى جهازك، ثم غير قيمة واحدة في كل خطأ وتوقع المخرج قبل التشغيل، وقارن. - اكتب اختباراً بـ
node:testلكل خطأ، يفشل مع النسخة الخاطئة وينجح مع الإصلاح، وشغله في CI. - أضف إلى
check.tsالخطأ 4 بـforEach(async)وشغلtsc --strict، ولاحظ هل يعترض. - استبدل كل استخدام لـ
Dateفي مشروع صغير لديك بـ Temporal، وقارن عدد الأسطر ووضوحها.
الخلاصة
وصلنا لنهاية الموضوع، وأهم ما فيه:
- JavaScript لا تصحح أخطاءها القديمة وإنما تضيف بجانبها طريقة جديدة، فالقادم من لغة أخرى يكتب ما يبدو طبيعياً فيصادف القديم.
- الوعود مع
forEachوifوfilterأعطت صفر عمليات محفوظة، ومنتجاً نافداً «متوفراً»، وتصفية لم تحذف شيئاً، والإصلاحfor...ofوawaitوPromise.all. - وفي الاتجاه المعاكس جعل
awaitداخل حلقة خمسة طلبات مستقلة تأخذ 252 ميلي ثانية بدلاً من 50، فابدأ العمليات المستقلة معاً وانتظرها بـPromise.all. sort()رتب 990 بعد 4500 وغير الأصل، وmap(parseInt)أعطىNaN، وfor...inأعطى"01"، وfill([])شارك مصفوفة واحدة، ولكل منها بديل حديث في سطر.thisيحدد عند الاستدعاء، وvarبنطاق الدالة، والكائنات تقارن بالهوية.- تاريخ بتوقيت UTC وضع طلب نوفمبر في أكتوبر، و
sliceقطع النخلة من منتصفها، والإصلاحtimeZoneوTemporal وIntl.Segmenter. npx tscفي مجلد بلا TypeScript ثبت حزمة غريبة، فشغل الأدوات من سكربتات المشروع.- TypeScript الصارم أمسك خطأ واحداً من سبعة، وBiome واحداً من أربعة عشر، والاختبار بقيمة متوقعة يمسكها كلها.
وبهذا تكتمل مرحلة JavaScript وTypeScript، ويمكنك أن تقارن نفس المواضيع في لغة أخرى من المسار، فالخدمة نفسها والقياسات نفسها مبنية في كل لغة:
دليلJavaScript لمن يعرف لغة أخرى: Node.js مع TypeScript والأدواتإذا كنت تبرمج بلغة أخرى وانتقلت إلى فريق يعمل بـ Node.js وTypeScript، فهذا يومك الأول: الإصدار الصحيح، والصيغة مقارنة بما تعرفه، وأدوات العمل وهيكل المشروع بأوامر ومخرجات حقيقية.
دليلالأنواع Types والذاكرة والأخطاء في JavaScriptلماذا ظهر في الفاتورة 29.700000000000003، ولماذا انهارت الخدمة برسالة heap out of memory، وأين يهرب الخطأ من try/catch: قيم JavaScript وذاكرتها وأخطاؤها بقياسات وتشغيلات حقيقية على Node.js 26.
دليلالتزامن في JavaScript: الوعود Promises والعمال Workersمئة طلب HTTP نزلت من 5.6 ثانية إلى 284 ميلي ثانية بالوعود وحدها، وحساب ثقيل لم يتحرك إلا بالعمال: نموذج التزامن في Node.js 26 بقياسات حقيقية، مع سباق وجمود حقيقيين وإصلاحهما.
دليلخدمة طلبات حقيقية بـ Node.js: إطار Fastify مع PostgreSQLنبني خدمة طلبات متجر بـ Fastify وPostgreSQL 17 بمعاملة تقفل المخزون وتحسب الضريبة بالهللات، ونختبرها بعشرين طلباً متوازياً على مخزون عشرة، ثم نقيس الـ Docker Image والبدء والذاكرة والإنتاجية بنفس طريقة كل لغات المسار.
دليلأخطاء شائعة وأسلوب Go الصحيح Idiomatic Goثلاثة عشر خطأً يأتي بها المطور من Java وPython وJavaScript إلى Go، لكل منها تشغيل خاطئ حقيقي وإصلاحه، ثم ما يبحث عنه المراجع وخطة قراءة من المصادر الرسمية.
دليلTypeScript للمطورين: أي الأخطاء تكشفها الأنواع Types؟زرعنا تسعة أخطاء في كود سلة مشتريات وفحصناها بالمترجم tsc بثلاثة إعدادات، لنرى ما يكشفه الوضع الصارم وما يمر رغم كل الأنواع ولماذا تحتاج التحقق وقت التشغيل.سجل التحديثات
- أكتوبر 2026: كتابة المقال، وتشغيل كل الأخطاء على Node.js v26.13.0 مع TypeScript 7.0.2 وBiome 2.6.4 داخل Docker Image الرسمي node:26.13.0-slim.