المعرفة المتقدمة
يتعمق القسم في الأجزاء الداخلية للسلسلة. ستكون هذه المعرفة مفيدة لك إذا كنت تخطط للتعامل مع الرموز التعبيرية أو الرموز الرياضية أو الهيروغليفية النادرة أو غيرها من الرموز النادرة.
كما نعلم بالفعل، تعتمد سلاسل JavaScript على Unicode: يتم تمثيل كل حرف بتسلسل بايت من 1 إلى 4 بايت.
تسمح لنا JavaScript بإدراج حرف في سلسلة عن طريق تحديد كود Unicode السداسي العشري الخاص به باستخدام أحد هذه الرموز الثلاثة:
xXX
يجب أن يتكون XX
من رقمين سداسي عشري بقيمة تتراوح بين 00
و FF
، ثم xXX
هو الحرف الذي رمز Unicode الخاص به هو XX
.
نظرًا لأن تدوين xXX
يدعم رقمين سداسيين عشريين فقط، فيمكن استخدامه لأول 256 حرف Unicode فقط.
تتضمن هذه الأحرف الـ 256 الأولى الأبجدية اللاتينية وأحرف بناء الجملة الأساسية وبعض الأحرف الأخرى. على سبيل المثال، "x7A"
هو نفس "z"
(Unicode U+007A
).
تنبيه("x7A"); // ض تنبيه("xA9"); // ©، رمز حقوق النشر
uXXXX
XXXX
يجب أن يتكون من 4 أرقام ست عشرية بالضبط مع قيمة تتراوح بين 0000
و FFFF
، ثم uXXXX
هو الحرف الذي رمز Unicode الخاص به هو XXXX
.
يمكن أيضًا تمثيل الأحرف التي تحتوي على قيم Unicode أكبر من U+FFFF
بهذا الترميز، لكن في هذه الحالة، سنحتاج إلى استخدام ما يسمى بالزوج البديل (سنتحدث عن الأزواج البديلة لاحقًا في هذا الفصل).
تنبيه("u00A9"); // ©، مثل xA9، باستخدام التدوين السداسي المكون من 4 أرقام تنبيه("u044F"); // я، الحرف الأبجدية السيريلية تنبيه("u2191"); // ↑، رمز السهم لأعلى
u{X…XXXXXX}
X…XXXXXX
يجب أن تكون قيمة سداسية عشرية تتراوح من 1 إلى 6 بايت بين 0
و 10FFFF
(أعلى نقطة رمز محددة بواسطة Unicode). يتيح لنا هذا الترميز تمثيل جميع أحرف Unicode الموجودة بسهولة.
تنبيه("u{20331}"); // 佫، حرف صيني نادر (Unicode طويل) تنبيه ("u{1F60D}")؛ // ?، رمز الوجه المبتسم (رمز Unicode طويل آخر)
تحتوي جميع الأحرف المستخدمة بشكل متكرر على رموز مكونة من 2 بايت (4 أرقام سداسية عشرية). تحتوي الحروف في معظم اللغات الأوروبية، والأرقام، ومجموعات CJK الإيديولوجية الأساسية الموحدة (CJK - من أنظمة الكتابة الصينية واليابانية والكورية)، على تمثيل 2 بايت.
في البداية، كانت JavaScript تعتمد على ترميز UTF-16 الذي يسمح فقط بـ 2 بايت لكل حرف. لكن 2 بايت تسمح فقط بـ 65536 مجموعة وهذا لا يكفي لكل رمز ممكن من رموز Unicode.
لذلك يتم تشفير الرموز النادرة التي تتطلب أكثر من 2 بايت بزوج من الأحرف المكونة من 2 بايت يسمى "زوج بديل".
كأثر جانبي، يبلغ طول هذه الرموز 2
:
تنبيه ('؟'. الطول )؛ // 2، النص الرياضي الكبير X تنبيه ('؟'. الطول )؛ // 2، وجه بدموع الفرح تنبيه ('؟'. الطول )؛ // 2، شخصية صينية نادرة
وذلك لأن الأزواج البديلة لم تكن موجودة في الوقت الذي تم فيه إنشاء JavaScript، وبالتالي لا تتم معالجتها بشكل صحيح بواسطة اللغة!
لدينا في الواقع رمز واحد في كل سلسلة من السلاسل المذكورة أعلاه، ولكن خاصية length
تظهر طولًا قدره 2
.
قد يكون الحصول على رمز أمرًا صعبًا أيضًا، لأن معظم ميزات اللغة تتعامل مع الأزواج البديلة كحرفين.
على سبيل المثال، هنا يمكننا أن نرى حرفين فرديين في الإخراج:
تنبيه('?'[0] ); // يظهر رموز غريبة... تنبيه('?'[1] ); // ...قطع من الزوج البديل
قطع الزوج البديل ليس لها معنى بدون بعضها البعض. لذا فإن التنبيهات الموجودة في المثال أعلاه تعرض فعليًا البيانات المهملة.
من الناحية الفنية، يمكن أيضًا اكتشاف الأزواج البديلة من خلال رموزها: إذا كان الحرف يحتوي على الكود في الفاصل الزمني 0xd800..0xdbff
، فهو الجزء الأول من الزوج البديل. يجب أن يحتوي الحرف التالي (الجزء الثاني) على الكود الموجود في الفاصل الزمني 0xdc00..0xdfff
. يتم حجز هذه الفواصل الزمنية حصريًا للأزواج البديلة وفقًا للمعايير.
لذلك تمت إضافة الأساليب String.fromCodePoint وstr.codePointAt في JavaScript للتعامل مع الأزواج البديلة.
إنها في الأساس نفس String.fromCharCode وstr.charCodeAt، لكنها تتعامل مع الأزواج البديلة بشكل صحيح.
يمكن للمرء أن يرى الفرق هنا:
// charCodeAt ليس على دراية بالزوج البديل، لذا فهو يعطي رموزًا للجزء الأول من ?: تنبيه( '?'.charCodeAt(0).toString(16) ); // d835 // codePointAt على دراية بالزوج البديل تنبيه( '?'.codePointAt(0).toString(16) ); // 1d4b3، يقرأ كلا الجزأين من الزوج البديل
ومع ذلك، إذا أخذنا من الموضع 1 (وهذا غير صحيح إلى حد ما هنا)، فسيقوم كلاهما بإرجاع الجزء الثاني فقط من الزوج:
تنبيه( '?'.charCodeAt(1).toString(16) ); // دي سي بي 3 تنبيه( '?'.codePointAt(1).toString(16) ); // دي سي بي 3 // النصف الثاني من الزوج لا معنى له
ستجد المزيد من الطرق للتعامل مع الأزواج البديلة لاحقًا في فصل العناصر التكرارية. من المحتمل أن تكون هناك مكتبات خاصة بذلك أيضًا، ولكن لا يوجد شيء مشهور بما يكفي لاقتراحه هنا.
الوجبات الجاهزة: تقسيم السلاسل عند نقطة تعسفية أمر خطير
لا يمكننا تقسيم سلسلة في موضع عشوائي، على سبيل المثال، خذ str.slice(0, 4)
ونتوقع أن تكون سلسلة صالحة، على سبيل المثال:
تنبيه('مرحبا؟'.slice(0, 4)); // أهلاً [؟]
هنا يمكننا أن نرى شخصية غير مرغوب فيها (النصف الأول من زوج الابتسامة البديل) في الإخراج.
فقط كن على علم بذلك إذا كنت تنوي العمل بشكل موثوق مع أزواج بديلة. قد لا تكون مشكلة كبيرة، ولكن على الأقل يجب أن تفهم ما يحدث.
في العديد من اللغات، توجد رموز تتكون من الحرف الأساسي مع وضع علامة فوقه أو تحته.
على سبيل المثال، يمكن أن يكون الحرف a
هو الحرف الأساسي لهذه الأحرف: àáâäãåā
.
تحتوي معظم الأحرف "المركبة" الشائعة على رمز خاص بها في جدول Unicode. ولكن ليس جميعها، لأن هناك عدد كبير جدًا من التركيبات الممكنة.
لدعم التركيبات العشوائية، يسمح لنا معيار Unicode باستخدام عدة أحرف Unicode: الحرف الأساسي متبوعًا بواحد أو أكثر من أحرف "العلامة" التي "تزينها".
على سبيل المثال، إذا كان لدينا S
متبوعًا بحرف "النقطة أعلاه" الخاص (الرمز u0307
)، فسيتم عرضه كـ Ṡ.
تنبيه('Su0307' ); // ن
إذا كنا بحاجة إلى علامة إضافية أعلى الحرف (أو أسفله) - فلا مشكلة، فما عليك سوى إضافة حرف العلامة الضروري.
على سبيل المثال، إذا أضفنا حرفًا "نقطة أدناه" (الرمز u0323
)، فسيكون لدينا "S مع نقاط في الأعلى والأسفل": Ṩ
.
على سبيل المثال:
تنبيه( 'Su0307u0323' ); // ص
وهذا يوفر مرونة كبيرة، ولكنه أيضًا يمثل مشكلة مثيرة للاهتمام: قد يبدو حرفان متماثلين بصريًا، ولكن يتم تمثيلهما بتركيبات Unicode مختلفة.
على سبيل المثال:
دع s1 = 'Su0307u0323'; // Ṩ، S + نقطة بالأعلى + نقطة بالأسفل دع s2 = 'Su0323u0307'; // Ṩ، S + نقطة أدناه + نقطة أعلاه تنبيه( `s1: ${s1}, s2: ${s2}` ); تنبيه(s1 == s2); // خطأ على الرغم من أن الأحرف تبدو متطابقة (؟!)
لحل هذه المشكلة، توجد خوارزمية "تسوية Unicode" التي تنقل كل سلسلة إلى النموذج "العادي" الفردي.
يتم تنفيذه بواسطة str.normalize().
تنبيه( "Su0307u0323".normalize() == "Su0323u0307".normalize() ); // حقيقي
من المضحك أنه في حالتنا normalize()
يجمع في الواقع سلسلة من 3 أحرف في حرف واحد: u1e68
(S بنقطتين).
تنبيه ("Su0307u0323".تطبيع (). الطول )؛ // 1 تنبيه ("Su0307u0323".normalize() == "u1e68" ); // حقيقي
في الواقع، هذا ليس هو الحال دائما. والسبب هو أن الرمز Ṩ
"شائع بما فيه الكفاية"، لذلك قام منشئو Unicode بإدراجه في الجدول الرئيسي وأعطوه الكود.
إذا كنت تريد معرفة المزيد حول قواعد التسوية ومتغيراتها - فهي موصوفة في ملحق معيار Unicode: نماذج تسوية Unicode، ولكن بالنسبة لمعظم الأغراض العملية، فإن المعلومات الواردة في هذا القسم كافية.