يونيكود ولماذا ينكسر النص
يبدو النص سهلاً حتى يظهر اسم عميل كـ «Ã©»، أو تفشل كلمة مرور فيها إيموجي في التحقق، أو يختلف عدّاد تغريدة عن تطبيقك. هذه الأعطال نادراً ما تأتي من «أحرف غريبة» — بل من خلط ثلاث أفكار: كيف يسمّي Unicode الأحرف، وكيف تخزّن الترميزات بايتات، وماذا يدرك الإنسان كحرف واحد على الشاشة. الرمز المرئي ليس بايتاً واحداً وليس دائماً فهرساً واحداً في سلسلة JavaScript.
5 دقائق قراءة · آخر تحديث 2026-10-04

يونيكود يعيّن أرقاماً؛ والترميزات تخزّن بايتات
معيار Unicode يعيّن نقطة ترميز — رقماً يُكتب مثل U+0041 للحرف A اللاتيني الكبير — لكل حرف مجرد في فهرسه. الفهرس ليس خطاً: الخطوط تربط نقاط الترميز برموز. UTF-8 وUTF-16 وUTF-32 مخططات ترميز تحوّل تسلسلات نقاط الترميز إلى بايتات للملفات وHTTP وقواعد البيانات. تظهر الأخطاء حين يعامل البرنامج بايتات UTF-8 كأحرف Latin-1، أو حين يفترض نظامان ترميزات مختلفة دون إعلانها.
نفس الحرف، تمثيلات مختلفة
| المنظور | مثال للحرف é (U+00E9) |
|---|---|
| نقطة الترميز | U+00E9 (حرف واحد معيّن) |
| بايتات UTF-8 (hex) | C3 A9 (بايتان) |
| وحدات ترميز UTF-16 (hex) | 00 E9 (وحدة واحدة في BMP) |
| طول سلسلة JavaScript | 1 (وحدة UTF-16 واحدة لـ é في BMP) |
UTF-8 عملياً
UTF-8 بعرض متغيّر: حروف ASCII بايت واحد؛ كثير من الكتابات الأوروبية بايتان؛ معظم الإيموجي والكتابات النادرة أربعة. هو الترميز السائد على الويب (HTTP وJSON وHTML). حين يقول نظام «Unicode» في 2026، يقصد غالباً «UTF-8 على السلك ما لم يُذكر غير ذلك».
Character: é
UTF-8 bytes: 0xC3 0xA9
Mis-decoded as Latin-1: à © → often shown as "é" (mojibake)UTF-16 وسلاسل JavaScript
سلاسل JavaScript تسلسلات من وحدات ترميز UTF-16، لا نقاط Unicode. الأحرف خارج Basic Multilingual Plane (فوق U+FFFF) — كثير من الإيموجي وحروف Han نادرة — تأخذ وحدتين UTF-16 تُسمى surrogate pair. لذا String.length يعدّ وحدات ترميز لا أحرفاً مرئية، والفهرسة بـ str[i] قد تقطع زوج surrogate وتنتج محارف تالفة.
const smile = "😀";
console.log(smile.length); // 2 (surrogate pair)
console.log(smile[0] === smile[1]); // false
console.log([...smile].length); // 1 (iterates code points)
console.log(Array.from(smile).length); // 1الانتشار وArray.from يتكرّران على نقاط ترميز Unicode. للحدود في الإنتاج، فضّل Intl.Segmenter أو مكتبة واعية بالرسوم عند عدّ ما يراه المستخدمون.
تجمّعات الرسوم: ما يعدّه المستخدمون
قد يجمع الحرف المرئي عدة نقاط ترميز. حرف أساسي مع حركة حادة مركبة (e + ◌́) قد يُعرض كـ é لكنه يبقى نقطتين. أعلام الإيموجي أزواج من حرفي مؤشر إقليمي؛ معدّلات لون البشرة تلحق بإيموجي الأشخاص؛ سلاسل ZWJ تربط إيموجي العائلة. Unicode يحدّد حدود extended grapheme cluster لـ«ما يشبه حرفاً واحداً» في التحرير وحركة المؤشر.
رمز واحد، نقاط ترميز متعددة
| مرئي | نقاط الترميز (مفهومياً) | فخ JS .length |
|---|---|---|
| é (مركّب مسبقاً) | U+00E9 | 1 وحدة ترميز |
| é (e + حركة حادة مركبة) | U+0065 U+0301 | 2 وحدة ترميز |
| 👨👩👧👦 (عائلة ZWJ) | عدة نقاط + ZWJ U+200D | .length ≫ 1 |
| 🇫🇷 (علم) | مؤشران إقليميان | .length === 4 |
| 1️⃣ (رقم keycap) | U+0031 U+FE0F U+20E3 | .length === 3 |
| 👍🏽 (لون بشرة) | U+1F44D U+1F3FD | .length === 4 |
const segmenter = new Intl.Segmenter("en", { granularity: "grapheme" });
const text = "e\u0301"; // e + combining acute
console.log(text.length); // 2
console.log([...segmenter.segment(text)].length); // 1 graphemeconst bio = "Hi 👨👩👧👦!"; // family ZWJ sequence
console.log(bio.length); // 11 UTF-16 code units
console.log([...bio].length); // 8 Unicode code points
console.log([...new Intl.Segmenter("en", { granularity: "grapheme" }).segment(bio)].length); // 7 graphemes (letters, space, family emoji, !)
// A "max 10 characters" rule using .length rejects this bio even though it looks short.النص المشوّه (mojibake) وإصلاح أخطاء الترميز
mojibake (نص مشوّش) يعني عادة أن بايتات فُسّرت بمفكّك خاطئ. الحالة الكلاسيكية: بايتات UTF-8 قُرئت كـ Windows-1252 أو ISO-8859-1. الإصلاح إعادة قراءة تدفق البايت الأصلي بالترميز الذي أُنتج به، ثم تحويل إلى UTF-8 — لا استبدال «Ã©» بـ «é» يدوياً في سلسلة فقدت معلومات.
بايتات داخلة
رفع ملف أو جسم HTTP أو BLOB في قاعدة البيانات
اكتشاف أو إعلان الترميز
BOM أو charset في Content-Type أو بيانات وصفية
فك إلى Unicode
نقاط ترميز في الذاكرة بلغتك
معالجة
بحث وتحقق وعرض — على نص Unicode
ترميز عند الإخراج
تقريباً دائماً UTF-8 لواجهات الويب
حوّل عند الحواف؛ احتفظ بـ Unicode في الوسط.
الإيموجي وحدود الطول
حدود المنتج — «140 حرفاً»، مقاطع SMS، VARCHAR في قاعدة البيانات — تختار قاعدة عدّ. عدّ Twitter التاريخي استخدم وحدات ترميز موزونة؛ SMS يستخدم GSM-7 أو مقاطع UCS-2؛ قواعد البيانات قد تعدّ بايتات. رسالة غنية بالإيموجي قد تتجاوز ميزانية بايتات بينما تبدو قصيرة بصرياً. وثّق أي مقياس يعرضه العداد: بايتات، وحدات UTF-16، نقاط ترميز، أو رسوم.
- حدود التخزين (bytea في PostgreSQL مقابل طول char) تهتم غالباً بالبايتات في UTF-8.
- maxlength في JavaScript على المدخلات يعدّ غالباً وحدات UTF-16.
- لوائح أو فوترة قد تعدّ رسوماً أو صفحات ترميز legacy.
- Base64 يوسّع UTF-8 الثنائي — ترميز النص للروابط ليس مثل عدّ الأحرف.
التطبيع: حين تفشل المساواة
نفس السلسلة المرئية قد يكون لها تمثيلات Unicode متعددة. NFC وNFD يختاران تسلسلات مركّبة أو مفكّكة قياسية. كلمات المرور وأسماء المستخدمين يجب مقارنتها بعد تطبيع متفق عليه — وإلا تحرم مستخدمين ينتج IME لديهم تسلسلاً مختلفاً لكن مكافئاً.
طي الحالة والlocale
I التركية المنقطة وغير المنقطة، وß الألمانية، ومتغيّرات sigma اليونانية تبيّن لماذا toLowerCase() وحده استراتيجية مساواة ضعيفة. case folding في Unicode والفرز الواعي بالسياق المحلي ينتميان لفهارس البحث وسياسات أسماء المستخدمين، لا إلى حيل نصية ad hoc في كود التطبيق.
Surrogates وتسلسلات غير صالحة
UTF-8 يسمح فقط بتسلسلات بايت سليمة؛ surrogates منفردة غير قانونية في UTF-8 لكنها قد تظهر عند سوء التعامل مع بيانات UTF-16. محلّلات تستبدل بصمت U+FFFD قد تخفي فساد بيانات — سجّل وارفض إدخالاً غير صالح عند حدود الثقة حين تهم السلامة.
قائمة تحقق للمطوّرين
- أعلن charset=utf-8 على استجابات HTML وJSON.
- لا تفترض بايتاً واحداً لكل حرف في أحجام المخازن المؤقتة.
- لا تستخدم طول السلسلة وحده لحدود مواجهة المستخدم.
- استخدم Intl.Segmenter أو ICU لحدود تحرير واعية بالرسوم.
- اختبر بعلامات مركبة وأعلام إيموجي وسلاسل ZWJ في مجموعات QA.
- عند دمج CSV قديم أو تصدير Windows، اسأل أي ترميز أنشأ الملف.
عدادات الأحرف والكلمات في SmartKitio يجب قراءتها مع وضع العدّ الموثّق — قارن بنتائج منصتك قبل فرض حدود في الإنتاج.
البيانات الثنائية ليست نصاً
Base64 وhex تمثّل بايتات، لا سلاسل Unicode. فك Base64 ينتج بايتات لا بد من فكها بـ UTF-8 (أو charset صحيح) قبل معاملة النتيجة كنص. تمرير ثنائي عبر واجهات string دون خطوة ترميز صريحة يعيد mojibake في طبقة أخرى — شائع في مسارات الرفع ومرفقات البريد.
ما يجب تذكّره
- Unicode يسمّي الأحرف كنقاط ترميز؛ UTF-8 (وغيره) يخزّن تلك النقاط بايتات.
- JavaScript .length يعدّ وحدات UTF-16 — أزواج surrogate وتسلسلات مركبة تكسر حدوداً ساذجة.
- المستخدمون يعدّون تجمّعات رسوم؛ استخدم Intl.Segmenter أو ICU حين تقول قواعد المنتج «أحرف».
- mojibake غالباً فك ترميز خاطئ عند حد — أصلح المسار لا السلسلة بالبحث والاستبدال.
- طبّع (غالباً NFC) قبل مقارنة كلمات مرور وأسماء مستخدمين من IME مختلفة.
قراءة إضافية
مقالات ذات صلة

التقنية
لماذا تستخدم البرمجيات تنسيقات بيانات مختلفة
JSON وCSV والثنائي والصور تحل قيوداً مختلفة — التبادل ليس مجانياً أبداً.
5 دقائق قراءة · 2026-10-04
اقرأ المقال
البرمجة
الترميز مقابل التشفير: ما الفرق؟
الترميز والتشفير والتجزئة والضغط يحلّ كلٌّ منها مشكلة مختلفة. الخلط بينها — خاصة Base64 والسرية — يسبب إخفاقات أمنية حقيقية.
5 دقائق قراءة · 2026-10-04
اقرأ المقال
الويب
المعالجة في جانب العميل مقابل جانب الخادم
مكان تنفيذ الحساب يحدّد الخصوصية والكمون والتكلفة والثقة. خريطة عملية لعمل المتصفح مقابل عمل الخادم — وماذا تفعل التطبيقات الهجينة فعلياً.
5 دقائق قراءة · 2026-10-04
اقرأ المقال
الويب
النسب المئوية في الحياة اليومية
إكراميات وضرائب وخصومات وفائدة وإحصاءات وعناوين — صيغ، نقاط نسبة مقابل تغيّر نسبي، وأمثلة محلولة يمكنك التحقق منها.
6 دقائق قراءة · 2026-10-04
اقرأ المقال
