JavaScript بعمق
التعبيرات النمطية في JavaScript: مطابقة الأنماط بلا خوف
دليل عملي للتعبيرات النمطية في JavaScript — الصياغة الحرفية مقابل الباني، وأصناف المحارف، والمكمّمات، والمرابط، والمجموعات والبدائل، والأعلام، وتوابع السلاسل (test وmatch وmatchAll وreplace وsplit)، والالتقاط والمجموعات المسمّاة، والاستباق/الاستلحاق، والمزالق الشائعة — مع تمارين عملية وحلولها.
التعبيرات النمطية هي الأداة التي يتجنّبها الجميع ثم يحتاجونها بهدوء كل أسبوع — التحقّق من المُدخَلات، واستخراج البيانات، والبحث-والاستبدال الأذكى من سلسلة حرفية. تبدو كضجيج أسطر، لكنها مبنية من مجموعة صغيرة من القطع التي تتركّب بتوقّع. تعلّم تلك القطع ويتبخّر الرهبة؛ ستقرأ الأنماط وتكتبها كما تقرأ الشيفرة. هذه المجموعة الفرعية العملية تغطّي الغالبية العظمى من الاستخدام الحقيقي. (يبني على السلاسل من أساسيات JavaScript.)
التعبير النمطيّ يصف نمطًا، لا سلسلة ثابتة. تجمّعه من أصناف المحارف (أي نوع محرف)، والمكمّمات (كم عدد)، والمرابط (أين)، والمجموعات (الالتقاط والبنية). كل نمط ستكتبه تقريبًا تركيب من هذه الأفكار الأربع.
إنشاء تعبير نمطي
صياغتان. فضّل الحرفية ما لم يُبنَ النمط من متغيّرات وقت التشغيل:
const re1 = /\d+/g; // حرفية — بين شرطتين، مع أعلام
const re2 = new RegExp("\\d+", "g"); // باني — لاحظ الشرطات المضاعفة
// الباني للأنماط الديناميكية
const word = "cat";
const re3 = new RegExp(`\\b${word}\\b`, "i");
في صيغة الباني، يجب تهريب الشرطات المائلة الخلفية (\\d)، ولهذا تكون الحرفية أنظف حين تستطيع استخدامها.
أصناف المحارف
هذه تطابق أنواع المحارف:
. أي محرف (عدا السطر الجديد)
\d \D رقم / غير رقم
\w \W محرف كلمة [A-Za-z0-9_] / غير كلمة
\s \S مسافة بيضاء / غير مسافة
[abc] أي واحد من a, b, c
[^abc] أي محرف عدا a, b, c
[a-z] مدى
/[aeiou]/.test("sky"); // false — بلا حرف علّة
/[0-9]/.test("a1b"); // true — فيه رقم
/[^0-9]/.test("123"); // false — كل محرف رقم
المكمّمات
هذه تقول كم من العنصر السابق:
* 0 أو أكثر
+ 1 أو أكثر
? 0 أو 1 (اختياري)
{3} 3 بالضبط
{2,4} 2 إلى 4
{2,} 2 أو أكثر
/colou?r/.test("color"); // true — الـ u اختيارية
/\d{3}-\d{4}/.test("555-1234"); // true — 3 أرقام، شرطة، 4 أرقام
المكمّمات جشعة افتراضيًّا — تطابق أكبر قدر ممكن. أضف ? لجعلها كسولة (أقلّ قدر ممكن)، ما يهمّ عند الاستخراج بين فواصل:
"<a><b>".match(/<.+>/)[0]; // "<a><b>" — جشع، يلتقط كل شيء
"<a><b>".match(/<.+?>/)[0]; // "<a>" — كسول، يتوقّف عند أول >
المرابط والحدود
هذه تطابق موضعًا، لا محرفًا:
^ بداية السلسلة (أو السطر، مع العلم m)
$ نهاية السلسلة (أو السطر)
\b حدّ كلمة
/^https/.test("https://x"); // true — يبدأ بـ https
/\.com$/.test("a.com"); // true — ينتهي بـ .com
/\bcat\b/.test("the cat sat"); // true — "cat" ككلمة كاملة
/\bcat\b/.test("category"); // false — ليست كلمة مستقلّة
\b هو ما يفصل "جِد الكلمة cat" عن "جِد c-a-t في أي مكان" — أساسيّ لمطابقات الكلمة الكاملة.
المجموعات والبدائل
الأقواس تجمّع الأجزاء (للمكمّمات أو الالتقاط)؛ والشرطة | هي أو:
/(ab)+/.test("abab"); // true — المجموعة تتكرّر
/cat|dog/.test("a dog"); // true — أيّ بديل
// مجموعات الالتقاط — استخرج القطع المطابِقة
const m = "2024-01-15".match(/(\d{4})-(\d{2})-(\d{2})/);
m[1]; // "2024" — المجموعة الأولى
m[2]; // "01"
m[3]; // "15"
// المجموعات المسمّاة — أوضح من المرقّمة
const d = "2024-01-15".match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/);
d.groups.year; // "2024"
d.groups.month; // "01"
مجموعة (?:...) غير ملتقِطة — تجمّع دون إنشاء التقاط مرقّم، ما يُبقي فهارس الالتقاط نظيفة.
الأعلام
الأعلام بعد الشرطة الختامية تغيّر السلوك:
g عام — جِد كل المطابقات، لا الأولى فقط
i غير حسّاس للحالة
m متعدّد الأسطر — ^ و$ تطابقان بدايات/نهايات الأسطر
s dotall — . تطابق السطور الجديدة أيضًا
u يونيكود — معالجة سليمة لنقاط الترميز
"Hello HELLO".match(/hello/gi); // ["Hello", "HELLO"] — عام + غير حسّاس
توابع السلاسل
التعبير النمطيّ يُستخدَم عبر توابع السلاسل/RegExp — ومعرفة أيّها تستخدم نصف المعركة:
// test — هل يطابق؟ → boolean
/\d/.test("abc1"); // true
// match — أول مطابقة (أو كلها، مع g)
"a1b2".match(/\d/); // ["1", index: 1, ...]
"a1b2".match(/\d/g); // ["1", "2"]
// matchAll — كل مطابقة مع مجموعات التقاطها (يحتاج g)
[..."a1b2".matchAll(/(\w)(\d)/g)]; // كائنات مطابقة مفصّلة
// replace — استبدل؛ $1 يشير إلى مجموعة
"2024-01-15".replace(/(\d{4})-(\d{2})-(\d{2})/, "$3/$2/$1"); // "15/01/2024"
"hello".replace(/l/g, "L"); // "heLLo" (g للكلّ)
// replace بدالّة — احسب كل استبدال
"a1b2".replace(/\d/g, (d) => d * 2); // "a2b4"
// split — قسّم على نمط
"a, b,c , d".split(/\s*,\s*/); // ["a", "b", "c", "d"]
استخدم test لنعم/لا، وmatch/matchAll للاستخراج، وreplace للتحويل، وsplit للتجزئة.
الأخطاء الشائعة
- نسيان العلم
gحين تريد كل المطابقات —match/replaceتفعلان الأولى فقط بدونه. - إعادة استخدام تعبير بعلم
gمعtest()في حلقة —lastIndexذو حالة ويتخطّى مطابقات. - مكمّمات جشعة تلتقط أكثر من اللازم — استخدم الكسول
*?/+?للتوقّف مبكرًا. - عدم تهريب المحارف الخاصّة (
. * + ? ( ) [ ]) عند مطابقتها حرفيًّا —\.للنقطة. - نسيان
^/$ومطابقة سلسلة فرعية بالخطأ بدل السلسلة كاملة. - بناء تعابير ديناميكية من مُدخَلات المستخدم بلا تهريب — خطر صحّة وReDoS.
- اللجوء للتعبير النمطيّ لتحليل HTML أو بنى متداخلة — استخدم محلّلًا حقيقيًّا؛ التعبير لا يعالج التداخل.
تمارين
جرّب كلًّا منها قبل فتح الحل.
تمرين 1 — تحقّق من نمط بسيط
اكتب تعبيرًا يختبر ما إذا كانت سلسلة 5 أرقام بالضبط.
إظهار الحل
/^\d{5}$/.test("12345"); // true
/^\d{5}$/.test("1234"); // false
/^\d{5}$/.test("12345x"); // false — مُرسى، بلا محارف لاحقة
^ و$ يُرسيان السلسلة كاملة، و\d{5} يتطلّب خمسة أرقام بالضبط — بلا المرابط، سيطابق "12345x" جزء الأرقام.
تمرين 2 — استخرج أجزاء تاريخ
من "2024-03-09"، اسحب السنة والشهر واليوم باستخدام مجموعات مسمّاة.
إظهار الحل
const { year, month, day } =
"2024-03-09".match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/).groups;
// year "2024"، month "03"، day "09"
مجموعات الالتقاط المسمّاة تضع القطع على .groups بمفاتيح مقروءة، وهذا أوضح من الاعتماد على m[1] وm[2] وm[3].
تمرين 3 — استبدل الكلّ وحوّل
أخفِ كل رقم في "id 4815" بـ *.
إظهار الحل
"id 4815".replace(/\d/g, "*"); // "id ****"
العلم g يجعل replace يصيب كل رقم، لا الأول فقط؛ وكل مطابقة تُبدَّل بـ *.
تمرين 4 — قسّم على مسافة مرنة
قسّم "one, two ,three" إلى رموز نظيفة، متحمّلًا المسافات حول الفواصل.
إظهار الحل
"one, two ,three".split(/\s*,\s*/); // ["one", "two", "three"]
\s*,\s* يطابق فاصلة بأي قدر من المسافة المحيطة، فيُنتج split رموزًا مشذّبة مباشرةً.
النموذج الذهني الذي تحتفظ به
التعبير النمطيّ نمط مُجمَّع من أربعة أنواع قطع: أصناف المحارف (\d، \w، [a-z] — ماذا)، والمكمّمات (*، +، {n} — كم، جشعة ما لم تضِف ?)، والمرابط (^، $، \b — أين)، والمجموعات (( ) تلتقط، (?: ) لا، | تبدّل). قُده عبر التابع الصحيح: test لنعم/لا، match/matchAll للاستخراج (بمجموعات مسمّاة للوضوح)، replace للتحويل (بـ $1 أو دالّة)، split للتجزئة — وتذكّر العلم g حين تريدها كلها. هرّب الحرفيات التي تقصدها حرفيًّا، وفضّل الأنماط المُرساة لتجنّب مطابقة السلاسل الفرعية بالخطأ، والجأ إلى محلّل حقيقيّ حين تتداخل البنية. مبنيًّا من تلك القطع القليلة، يتوقّف التعبير النمطيّ عن كونه ضجيج أسطر ويصير أداة دقيقة مقروءة.