هذا المقال لمستوى: مبتدئ
يونيكود وUTF-8 للمبتدئ: ليه طول النص بيطلع غلط مع الإيموجي والعربي
لو بتحدّد طول اسم المستخدم بـ name.length، فيه احتمال كبير إن الكود بتاعك بيقص أسماء عربية غلط وبيحسب الإيموجي خانتين. المقال ده هيوريك بالظبط ليه بيحصل ده، وإزاي تعدّه في سطر واحد. اللي هتكسبه: تعرف الفرق بين ثلاث حاجات الناس بتخلط بينها، البايت، ووحدة الترميز، ونقطة الرمز.
المشكلة باختصار
عندك خانة اسم في فورم تسجيل، وحاطط شرط إن الاسم ما يزيدش عن 10 حروف. المستخدم كتب اسمه ومعاه إيموجي بسيط. فجأة الشرط بيرفض اسم قصير، أو بيقصّه في النص فيطلع محرف مكسور. المشكلة مش في المتصفح ولا في المستخدم، المشكلة إن length مش بيعدّ الحروف اللي انت شايفها.
مثال بسيط الأول
تخيّل إنك بتشحن طرود في شاحنة. القاعدة عندك: كل صنف بياخد صندوق واحد. ده صح طول ما الأصناف كلها صغيرة. لكن أول ما ييجي صنف كبير بياخد أربع صناديق، عدّة الصناديق بتاعتك بتبقى غير عدّة الأصناف. الحروف زي كده بالظبط. الحرف الإنجليزي A بياخد صندوق واحد، الحرف العربي بياخد صندوقين، والإيموجي ممكن ياخد أربعة. لو انت بتعدّ الصناديق وفاكرها أصناف، الرقم هيطلع غلط.
الشرح العلمي: ثلاث كلمات لازم تفرّق بينهم
النص عند الكمبيوتر بيمر بثلاث طبقات، وكل طبقة ليها طول مختلف:
- نقطة الرمز (Code Point): الرقم اللي يونيكود بيدّيه لكل حرف. مثلًا
🌍رقمها U+1F30D. دي أقرب حاجة للحرف اللي انت شايفه. - وحدة الترميز (Code Unit): الطريقة اللي الذاكرة بتخزّن بيها نقطة الرمز. جافاسكريبت بتستخدم UTF-16 داخليًا، فأي رمز فوق U+FFFF بيتخزّن في وحدتين (اسمهم surrogate pair). وده اللي
lengthبيعدّه فعلًا. - البايت (Byte): لمّا تحفظ النص في ملف أو قاعدة بيانات بترميز UTF-8، كل نقطة رمز بتتحوّل لبايت لحد أربعة بايتات.
يعني كلمة "طول النص" لوحدها من غير ما تحدد أي طول، جملة ناقصة.
الكود اللي بيحصل فعلاً
// جافاسكريبت بتعدّ وحدات UTF-16، مش الحروف
"🌍".length // 2 ← وحدتان (surrogate pair)
[..."🌍"].length // 1 ← نعدّ نقاط الرمز الصح
// الاسم العائلي للإيموجي أسوأ
"👨👩👧".length // 5 ← تلات وجوه + محرفَي ربط خفيين
[..."👨👩👧"].length // 5 برضه، لأنها نقاط رمز متعددة
// عدد البايتات في UTF-8 (Node.js)
Buffer.byteLength("🌍", "utf8") // 4
Buffer.byteLength("م", "utf8") // 2
Buffer.byteLength("A", "utf8") // 1