String vs string: القاتل الصامت لأداء كودك
\n\nهل تساءلت يومًا لماذا في مكان ما يشتكي TypeScript من String، وفي مكان آخر من string؟ أو ربما رأيت مطورين متمرسين يتجهمون عند رؤية new String("hello")؟ هذا السؤال هو نجم حقيقي على Stack Overflow، حاصدًا آلاف المشاهدات. وليس من دون سبب. الفرق بين String و string ليس مجرد نزوة لغوية، بل هو اختلاف جوهري يؤثر على أداء وأمان وقابلية قراءة كودك.
في هذه المقالة، لن نقدم مجرد تعريف جاف. سنحلل ما يكمن وراء كل من هذه الأنواع، وكيف تتصرف في وقت التشغيل، ولماذا الاختيار الصحيح يمكن أن يسرّع تطبيقك بعشرات النسبة المئوية. استعدوا، سيكون الأمر ممتعًا!
\n\nالبدائيات مقابل الكائنات: أساسيات الأساسيات
\n\nلفهم الفرق، علينا أن نتذكر كيف يعمل JavaScript (وبالتالي TypeScript) مع البيانات. هناك نوعان من القيم في اللغة:
\n\n- \n
- البدائيات (primitives):
string،number،boolean،null،undefined،symbol،bigint. وهي غير قابلة للتغيير وتُخزَّن بالقيمة. \n - الكائنات (objects):
Object،Array،Function، وكذلكString(بحرف كبير)،Number،Boolean. وهي قابلة للتغيير وتُخزَّن بالمرجع. \n
عندما تكتب let name: string = "Alice"، فإنك تنشئ بدائيًا. وعندما تكتب let name: String = new String("Alice")، فإنك تنشئ كائنًا غلافًا. وهنا يبدأ الجزء المثير للاهتمام.
النوع string (بحرف صغير)
\nهذا نوع بدائي. يُستخدم في 99.9% من الحالات. إنه خفيف الوزن، سريع، ويمكن التنبؤ به. مقارنة بدائيين تتم حسب القيمة:
\n\nlet a: string = "hello";\nlet b: string = "hello";\nconsole.log(a === b); // true\n\nيوصي TypeScript باستخدامه تحديدًا. إنه المعيار الفعلي للإعلان عن المتغيرات النصية، ومعاملات الدوال، وقيم الإرجاع.
\n\nالنوع String (بحرف كبير)
\nهذا نوع كائني، مُنشئ غلاف. إنه موجود منذ أيام JavaScript للعمل مع دوال النصوص. ومع ذلك، فإن استخدامه المباشر هو نمط مضاد إلى حد كبير، إلا إذا كانت لديك مهمة محددة جدًا. مقارنة كائنين من String تتم حسب المرجع:
let a: String = new String("hello");\nlet b: String = new String("hello");\nconsole.log(a === b); // false! هذان كائنان مختلفان في الذاكرة\n\nهذا يمكن أن يؤدي إلى أخطاء يصعب تتبعها. تخيل أنك تتحقق مما إذا كانت قيمة النص تساوي "hello"، وتحصل على false فقط لأن شخصًا ما استخدم new String().
الأداء: لماذا يفوز string؟
\n\nالآن ننتقل إلى الجزء الأكثر إثارة — الأرقام والتحسين. دعنا نكتب اختبار أداء بسيط (يمكنك تشغيله في بيئتك، مثل Node.js أو المتصفح).
\n\n// اختبار الأداء\nconst iterations = 1_000_000;\n\nconsole.time("string (primitive)");\nlet sum = "";\nfor (let i = 0; i < iterations; i++) {\n sum += "a";\n}\nconsole.timeEnd("string (primitive)");\n\nconsole.time("String (object)");\nlet sumObj = new String("");\nfor (let i = 0; i < iterations; i++) {\n sumObj += "a"; // هنا يحدث التغليف التلقائي وإلغاء التغليف\n}\nconsole.timeEnd("String (object)");\n\nستكون النتيجة على جهاز نموذجي مختلفة بشكل كبير (لصالح البدائي بمقدار 2-5 مرات). لماذا؟ لأن:
\n- \n
- البدائي
stringهو مجرد سلسلة من الأحرف في الذاكرة. العمليات عليه مُحسَّنة على مستوى المحرك. \n