كائن `String`

تشرح هذه المقالة كائن String۔

تغطي الشرح جميع الأمور من الأساسيات إلى التقنيات المتقدمة، بما في ذلك المزالق المتعلقة بـ Unicode والتعبيرات النمطية، خطوة بخطوة وبطريقة سهلة الفهم۔

YouTube Video

كائن String

السلاسل النصية في جافاسكريبت هي من أكثر الأنواع استخدامًا في التطوير اليومي۔

الفرق بين السلاسل البدائية وكائنات السلاسل

السلاسل البدائية (مثل "hello") تتصرف بشكل مختلف عن كائنات التغليف مثل new String("hello")۔ عادةً، يجب استخدام السلاسل البدائية، ولا توجد حاجة كبيرة لاستخدام الشكل الكائني۔

1// Primitive string
2const a = "hello";
3
4// String wrapper object
5const b = new String("hello");
6
7console.log(typeof a); // "string"
8console.log(typeof b); // "object"
9console.log(a === b);  // false — wrapper objects are not strictly equal
  • يعرض هذا الكود الفرق في النوع بين السلسلة البدائية وكائن التغليف، وكيف يتصرفان مع المقارنة الصارمة۔ في معظم الحالات، تجنب استخدام new String() والتزم بالسلاسل البدائية۔

طرق إنشاء السلاسل النصية (الثوابت والقوالب النصية)

القوالب النصية مفيدة لإدراج المتغيرات وكتابة سلاسل متعددة الأسطر۔ يمكنك إدراج المتغيرات وتقييم التعابير بشكل بديهي۔

1const name = "Alice";
2const age = 30;
3
4// Template literal
5const greeting = `Name: ${name}, Age: ${age + 1}`;
6
7console.log(greeting); // "Name: Alice, Age: 31"
  • القوالب النصية سهلة القراءة ومثالية لبناء سلاسل نصية معقدة، بما في ذلك السلاسل متعددة الأسطر۔

الطرق الشائعة (البحث واستخراج أجزاء السلسلة)

كائن String يحتوي على العديد من الطرق الأساسية۔

 1const text = "Hello, world! Hello again.";
 2
 3// search
 4console.log(text.indexOf("Hello"));       // 0
 5console.log(text.indexOf("Hello", 1));    // 13
 6console.log(text.includes("world"));      // true
 7console.log(text.startsWith("Hello"));    // true
 8console.log(text.endsWith("again."));     // true
 9
10// slice / substring
11console.log(text.slice(7, 12));           // "world"
12console.log(text.substring(7, 12));       // "world"
  • slice وsubstring متشابهان، لكنهما يتعاملان مع الفهارس السالبة بشكل مختلف۔ يفسر slice القيم السالبة كمواقع من النهاية۔ كن واضحًا بشأن أي واحد ستستخدم۔

التقسيم والانضمام (split / join)

من الشائع تقسيم السلسلة إلى مصفوفة للمعالجة ثم جمعها من جديد۔

1const csv = "red,green,blue";
2const arr = csv.split(","); // ["red","green","blue"]
3
4console.log(arr);
5console.log(arr.join(" | ")); // "red | green | blue"
  • النمط الشائع هو استخدام split لتقسيم السلسلة، ثم معالجة المصفوفة الناتجة باستخدام map أو filter، ثم استخدام join لإعادة الجمع۔

الاستبدال والتعبيرات النمطية

replace يستبدل المطابقة الأولى فقط۔ إذا كنت تريد استبدال جميع المطابقات، استخدم العلم g مع تعبير نمطي۔ بتمرير دالة كبديل، يمكنك أيضًا إجراء الاستبدال الديناميكي۔

 1const s = "foo 1 foo 2";
 2
 3// replace first only
 4console.log(s.replace("foo", "bar")); // "bar 1 foo 2"
 5
 6// replace all using regex
 7console.log(s.replace(/foo/g, "bar")); // "bar 1 bar 2"
 8
 9// replace with function
10const r = s.replace(/\d+/g, (match) => String(Number(match) * 10));
11console.log(r);    // "foo 10 foo 20"
  • مع الاستبدال الديناميكي باستخدام دالة، يمكنك كتابة كود يحدد ويحول المطابقات باختصار۔

تحويل الحالة والتطبيع

لدعم اللغات المتعددة والمقارنة، بالإضافة إلى toLowerCase و toUpperCase، فإن تطبيع يونيكود (normalize) مهم أيضًا.۔ هذا مهم بشكل خاص عند مقارنة الحروف التي تحتوي على علامات تشكيل۔

 1// Case conversion example:
 2// "\u00DF" represents the German letter "ß".
 3// In some locales, converting "ß" to uppercase becomes "SS".
 4// JavaScript follows this behavior.
 5console.log("\u00DF");
 6console.log("\u00DF".toUpperCase()); // "SS"
 7
 8// Unicode normalization example:
 9// "e\u0301" is "e" + a combining acute accent.
10// "\u00e9" is the precomposed character "é".
11// These two look the same but are different code point sequences.
12const a = "e\u0301";
13const b = "\u00e9";
14
15console.log(a === b);   // false: different underlying code points
16console.log(a.normalize() === b.normalize()); // true: normalized to the same form
  • أنماط Unicode المختلفة، مثل الحروف المركبة أو المتصلة، لن تكون متساوية كما هي، لذا استخدم normalize() قبل المقارنة۔

Unicode ونقاط الشيفرة (معالجة أزواج البديلة)

السلاسل النصية في جافاسكريبت هي تسلسل من وحدات ترميز UTF-16، لذلك قد يشغل بعض الحروف مثل الرموز التعبيرية وحدتي ترميز لحرف واحد۔ لمعالجة وحدات الحروف الفعلية، استخدم Array.from أو معامل الانتشار أو for...of۔

 1// Emoji composed with multiple code points:
 2// "\u{1F469}" = woman, "\u{200D}" = Zero Width Joiner (ZWJ),
 3// "\u{1F4BB}" = laptop. Combined, they form a single emoji: 👩‍💻
 4const s = "\u{1F469}\u{200D}\u{1F4BB}";
 5console.log(s);
 6
 7// Length in UTF-16 code units (not actual Unicode characters):
 8// Because this emoji uses surrogate pairs + ZWJ, the length may be > 1.
 9console.log("Length:", s.length);
10
11// Iterate by Unicode code points (ES6 for...of iterates code points):
12// Each iteration gives a full Unicode character, not UTF-16 units.
13for (const ch of s) {
14  console.log(ch);
15}
16
17// Convert to an array of Unicode characters:
18console.log(Array.from(s));
  • تعيد الخاصية length عدد وحدات الترميز، لذا قد لا تحصل على العدد المتوقع مع الرموز التعبيرية أو الحروف المتصلة۔ for...of وArray.from يعالجان شيئًا قريبًا من الحروف المعروضة (مجموعات الرسوم)، لكن إذا كنت بحاجة إلى دعم كامل للرسوم، ففكر في استخدام مكتبة متخصصة۔

الاستبدال الآمن للتعبيرات النمطية (عند التعامل مع مدخلات المستخدم)

إذا نسيت معالجة مدخلات المستخدم بشكل صحيح عند تضمينها في تعبير نمطي، فقد يؤدي ذلك إلى سلوك غير متوقع وثغرات أمنية.۔ دائمًا قم بهروب مدخلات المستخدم قبل استخدامها في النمط۔

1function escapeRegex(s) {
2  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3}
4
5const userInput = "a+b";
6const pattern = new RegExp(escapeRegex(userInput), "g");
7console.log("a+b a+b".replace(pattern, "X")); // "X X"
  • لا تستخدم السلاسل النصية للمستخدم مباشرة في التعبيرات النمطية؛ دائماً قم بهروبها قبل بناء التعبير۔

نصائح الأداء: الدمج والقوالب

عند دمج العديد من السلاسل الصغيرة بالتتابع، وضعها في مصفوفة واستخدام join قد يكون أكثر فعالية۔ من ناحية أخرى، تعتبر القوالب النصية سهلة القراءة وسريعة بما فيه الكفاية في معظم الحالات۔

 1// concatenation in loop (less ideal)
 2let s = "";
 3for (let i = 0; i < 1000; i++) {
 4  s += i + ",";
 5}
 6
 7// using array + join (often faster for many pieces)
 8const parts = [];
 9for (let i = 0; i < 1000; i++) {
10  parts.push(i + ",");
11}
12const s2 = parts.join("");
  • محركات جافاسكريبت الحديثة محسّنة للغاية، لذلك لا داعي للقلق بشأن الأداء عند دمج عدد صغير من السلاسل۔ ومع ذلك، إذا كنت بحاجة إلى دمج عشرات آلاف المرات، فإن استخدام join يمكن أن يكون أكثر كفاءة۔

تقنيات عملية مفيدة: الحشو، القطع، والتكرار

trim و padStart و padEnd وrepeat هي طرق مفيدة بشكل خاص في معالجة السلاسل النصية اليومية.۔ غالبًا ما تُستخدم هذه الطرق في الحالات العملية مثل تنسيق قيم الإدخال أو توحيد تنسيقات الإخراج.۔

1console.log("  hello  ".trim());       // "hello"
2console.log("5".padStart(3, "0"));     // "005"
3console.log("x".repeat(5));            // "xxxxx"
  • يمكن استخدام هذه الطرق لتطبيع مدخلات النماذج أو إنشاء مخرجات ذات عرض ثابت۔

مقارنة السلاسل (وفق البيئة المحلية)

localeCompare فعال لمقارنة السلاسل وفق ترتيب القاموس حسب اللغة۔ يمكنك تحديد اللغة وخيارات الحساسية (مثل حساسية الحالة)۔

1console.log(
2  "\u00E4".localeCompare("z", "de")
3); // may be -1 or other depending on locale
4
5console.log(
6  "a".localeCompare("A", undefined, { sensitivity: "base" })
7); // 0
  • للمقارنات الدولية، استخدم localeCompare وحدد البيئة المحلية والخيارات المناسبة۔

مثال عملي: تحويل صف CSV إلى كائن (سير عمل عملي)

حالة استخدام شائعة هي تحويل صف CSV مفرد إلى كائن باستخدام مزيج من split و trim و map۔ للحصول على الحقول المحاطة بعلامات اقتباس أو ملفات CSV معقدة، استخدم محلل CSV مخصص۔

 1// simple CSV parse (no quotes handling)
 2function parseCsvLine(line, headers) {
 3  const values = line.split(",").map(v => v.trim());
 4  const obj = {};
 5  headers.forEach((h, i) => obj[h] = values[i] ?? null);
 6  return obj;
 7}
 8
 9const headers = ["name", "age", "city"];
10const line = " Alice , 30 , New York ";
11console.log(parseCsvLine(line, headers));
12// { name: "Alice", age: "30", city: "New York" }
  • تعمل هذه الطريقة مع CSV البسيط، لكن انتبه أنها لا يمكنها التعامل مع الحالات التي يكون فيها فاصلة داخل حقل مقتبس۔

المشكلات الشائعة

هناك بعض المواصفات والسلوكيات التي يسهل تجاهلها في التعامل مع السلاسل النصية في جافاسكريبت۔ لتجنب الأخطاء غير المتوقعة، من المهم مراعاة النقاط التالية۔

  • يمكن أن يؤدي استخدام new String() إلى نتائج غير صحيحة عند التحقق من النوع أو المقارنات۔ في معظم الحالات، تكون الأنواع الأساسية للسلاسل كافية۔
  • في Unicode، قد يتكون الحرف المرئي الواحد من عدة وحدات ترميز۔ لذلك، قد لا تتطابق القيمة التي تعيدها length مع عدد الحروف الظاهرة الفعلي۔
  • عند إدخال مدخلات المستخدم في تعبير نمطي، قم دائمًا بهروبها أولاً۔
  • String.prototype.replace() يستبدل المطابقة الأولى فقط بشكل افتراضي۔ إذا كنت تريد استبدال جميع التكرارات، استخدم العلم /g في التعبير النمطي الخاص بك.۔
  • السلاسل النصية غير قابلة للتغيير، لذا فإن العمليات تعيد دائمًا سلسلة جديدة۔ من المهم دائمًا إسناد القيمة المرجعة۔

الملخص

حتى وإن بدت السلاسل النصية في جافاسكريبت بسيطة، من المهم فهم خصائصها فيما يتعلق بـ Unicode وعدم القابلية للتغيير۔ بإتقان الأساسيات، يمكنك تحسين موثوقية وقابلية قراءة معالجة السلاسل النصية بشكل كبير۔

يمكنك متابعة المقالة أعلاه باستخدام Visual Studio Code على قناتنا على YouTube.۔ يرجى التحقق من القناة على YouTube أيضًا.۔

YouTube Video