10 حيل في التعبيرات المنتظمة يجب أن يعرفها كل مطور
التعبيرات المنتظمة من تلك الأدوات التي تبدو مُبهمة في البداية، ثم تنقر فجأة في مكانها. حين تنقر، تبدأ في رؤيتها في كل مكان: التحقق من صحة المدخلات، تحليل السجلات، خطوط البحث والاستبدال، توجيه الروابط. لكن معظم المطورين لا يستخدمون سوى حفنة من الميزات — فئات الأحرف، المحددات الكمية، المراسي — ويتركون باقي المواصفات دون لمس.
يتناول هذا الدليل عشر ميزات في التعبيرات المنتظمة تتخطى الأساسيات. كلٌّ منها يحل مشكلة حقيقية لا تستطيع الأنماط الأبسط التعامل معها بنظافة. جميع الأمثلة تستخدم بنية JavaScript، وهي صالحة أيضاً لأي بيئة متوافقة مع ECMAScript.
يمكنك اختبار كل نمط في هذا المقال باستخدام Toova Regex Tester دون كتابة أي سطر من كود الإعداد.
1. النظرات للأمام: مطابقة دون استهلاك
تؤكد النظرة للأمام أن نمطاً يجب (أو لا يجب) أن يتبع الموضع الحالي، دون جعل محرك المطابقة يتقدم ما وراء تلك الأحرف. النص المُطابَق لا يشمل ما تتحقق منه النظرة للأمام.
النظرة للأمام الإيجابية: (?=...)
// النظرة للأمام الإيجابية: مطابقة "foo" فقط عند اتباعها بـ "bar"
const re1 = /foo(?=bar)/;
re1.test('foobar'); // true
re1.test('foobaz'); // false النظرة للأمام السلبية: (?!...)
// النظرة للأمام السلبية: مطابقة "foo" التي لا تتبعها "bar"
const re2 = /foo(?!bar)/;
re2.test('foobaz'); // true
re2.test('foobar'); // false
استخدام عملي: مطابقة رقم سعر فقط عند اتباعه برمز العملة، دون تضمين الرمز في القيمة الملتقطة. أو التحقق من أن كلمة المرور تحتوي على رقم واحد على الأقل باستخدام (?=.*\d) دون تحديد موضع الرقم.
النظرات للأمام بدون عرض — لا تستهلك أحرفاً. يمكن تكديس نظرات متعددة في الموضع ذاته لفرض شروط مستقلة متعددة في آن واحد.
2. النظرات للخلف: التحقق مما سبق
النظرة للخلف هي مرآة النظرة للأمام: تتحقق من النص الذي يسبق الموضع الحالي دون تضمينه في المطابقة.
النظرة للخلف الإيجابية: (?<=...)
// النظرة للخلف الإيجابية: مطابقة "bar" فقط عند سبقها بـ "foo"
const re3 = /(?<=foo)bar/;
re3.test('foobar'); // true
re3.test('bazbar'); // false النظرة للخلف السلبية: (?<!...)
// النظرة للخلف السلبية: مطابقة "bar" التي لا يسبقها "foo"
const re4 = /(?<!foo)bar/;
re4.test('bazbar'); // true
re4.test('foobar'); // false
وصلت النظرات للخلف في ECMAScript 2018 وهي مدعومة في جميع المتصفحات الحديثة وNode.js 10+. استخدام شائع: استخراج الجزء القيمي من زوج مفتاح-قيمة مثل name=Alice بمطابقة كل شيء بعد name= دون تضمين المفتاح في المطابقة.
ملاحظة: خلافاً للنظرات للأمام، لا يمكن لتعبيرات النظرة للخلف في JavaScript احتواء أنماط ذات طول متغير — يجب أن يكون لتعبير النظرة للخلف طول ثابت أو حد أقصى محدد.
3. المجموعات الملتقطة المسماة: أنماط موثقة ذاتياً
تُشار إلى مجموعات الالتقاط القياسية برقم: $1، $2، وهكذا. عند إضافة أو حذف مجموعة، ينكسر كل مرجع خلفي. المجموعات المسماة تحل هذا بالسماح لك بإرفاق تسمية لكل مجموعة.
const dateRe = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const m = '2026-05-10'.match(dateRe);
console.log(m.groups.year); // "2026"
console.log(m.groups.month); // "05"
console.log(m.groups.day); // "10"
المجموعات المسماة متاحة أيضاً في سلاسل الاستبدال عبر $<name>:
// مرجع خلفي باستخدام المجموعة المسماة في النمط
const quoteRe = /(?<q>['"]).*?\k<q>/;
quoteRe.test('"hello"'); // true
quoteRe.test('"hello''); // false
يجب أن يكون اسم المجموعة معرّفاً صالحاً في JavaScript. استخدم أسماء وصفية تعكس ما تلتقطه المجموعة — year، port، protocol — وستصبح أنماطك شبه موثقة ذاتياً. يمكنك أيضاً استخدام \k<name> داخل النمط نفسه للإشارة خلفياً إلى مجموعة مسماة.
4. المحددات الكمية غير الجشعة: مطابقة الحد الأدنى
افتراضياً، المحددات الكمية (*، +، ?) جشعة — تطابق أكبر عدد ممكن من الأحرف. إضافة ? بعد المحدد الكمي يجعله غير جشع (يُسمى أيضاً كسولاً أو مُتحفظاً)، يطابق أقل عدد ممكن من الأحرف.
const html = '<a>click</a>';
// جشع (افتراضي) — يطابق أطول سلسلة ممكنة
/<.+>/.exec(html)?.[0]; // '<a>click</a>'
// غير جشع — يطابق أقصر سلسلة ممكنة
/<.+?>/.exec(html)?.[0]; // '<a>' هذا مهم عند تحليل HTML أو XML أو أي تنسيق يمكن أن يظهر فيه المحدد ذاته عدة مرات. النسخة الجشعة تبتلع كل شيء من أول وسم فتح حتى آخر وسم إغلاق عبر النص كله. النسخة غير الجشعة تتوقف عند أول إغلاق صالح.
5. تجنب التراجع الكارثي
التراجع هو الطريقة التي تتعافى بها محركات التعبيرات المنتظمة من محاولة مطابقة فاشلة. في معظم الحالات لا يُلاحَظ وهو سريع. لكن بعض الأنماط يمكن أن تجعل المحرك يستكشف عدداً متزايداً أسياً من المسارات، مما يشل عملية Node.js حتى مع سلسلة إدخال متواضعة.
نمط الخطر الكلاسيكي هو المحددات الكمية المتداخلة مثل (a+)+ المطبقة على سلسلة مثل aaaaab. يحاول المحرك كل طريقة ممكنة لتوزيع أحرف a بين المجموعتين الداخلية والخارجية قبل أن يستنتج عدم وجود مطابقة.
المجموعات الذرية ((?>...)) تمنع ذلك بإخبار المحرك بعدم التراجع في مجموعة طابقت بنجاح. JavaScript لا تدعم المجموعات الذرية أصلاً، لكن يمكن محاكاة السلوك المالك باستخدام نظرة للأمام:
// JavaScript لا تدعم المجموعات الذرية نيتياً،
// لكن يمكن محاكاتها باستخدام نظرة للأمام:
const re5 = /(?=(\d+))\1(?!\d)/; // محاكاة possessive \d++ 6. عمليات مجموعة فئات الأحرف (علامة Unicode v)
أدخل ECMAScript 2024 علامة v التي تتيح عمليات المجموعة داخل فئات الأحرف. يتيح لك ذلك التعبير عن "جميع الحروف ما عدا حروف العلة" أو "الأحرف الكبيرة الإنجليزية ASCII" كتعريف فئة نظيف بدلاً من تبديل مُرهق.
// وضع Unicode sets (علامة v):
const lettersNoVowels = /[a-z--[aeiou]]/v;
lettersNoVowels.test('b'); // true
lettersNoVowels.test('e'); // false
تدعم علامة v ثلاث عمليات داخل فئات الأحرف:
- الطرح:
[A--B]— أحرف في A وليست في B - التقاطع:
[A&&B]— أحرف في A وB معاً - الاتحاد:
[AB]— أحرف في A أو B
7. حدود الكلمات: مطابقة الكلمة الكاملة
يطابق المرساة \b الموضع بين حرف كلمة (\w) وحرف غير كلمة. لا يستهلك أحرفاً — يؤكد الموضع فحسب. عكسه \B يطابق أي موضع ليس حد كلمة.
const sentence = 'cat concatenate';
// بدون \b — يجد "cat" داخل "concatenate" أيضاً
/cat/g.exec(sentence);
// مع \b — يطابق فقط الكلمة المستقلة "cat"
/\bcat\b/g.exec(sentence); // \B عكسه: يطابق داخل كلمة، لا عند حدودها
/\Bcat\B/.test('concatenate'); // true
حدود الكلمات ضرورية عند البحث عن معرّفات في الكود أو النثر. بدونها، سيُصادف البحث عن متغير اسمه id أيضاً indexOf وinvalid وgrid.
8. وضع متعدد الأسطر: مراسٍ لكل سطر
افتراضياً، ^ يطابق بداية النص فقط و$ يطابق نهايته فقط. علامة m (متعدد الأسطر) تغير هذا: ^ يطابق بداية كل سطر و$ يطابق نهاية كل سطر.
const text = 'line one\nline two\nline three';
// بدون علامة m — ^ يطابق بداية النص فقط
/^line/.test(text); // true (السطر الأول فقط)
// مع علامة m — ^ يطابق بداية كل سطر
const matches = text.match(/^line/gm);
console.log(matches); // ['line', 'line', 'line']
لا تخلط بين علامة m وعلامة s (dotAll). علامة s تجعل . يطابق أحرف السطر الجديد أيضاً. علامة m لا تؤثر على . إطلاقاً — فقط على سلوك ^ و$.
9. هروبات خصائص Unicode: مطابقة الأحرف الدولية
تتيح علامة u هروبات خصائص Unicode، مما يسمح لك بمطابقة الأحرف بناءً على فئة Unicode أو خط كتابتها أو خصائص أخرى. هذه الطريقة الصحيحة لمطابقة الحروف والأرقام أو علامات الترقيم عبر جميع أنظمة الكتابة البشرية.
// علامة u تتيح Unicode property escapes
const letters = /\p{L}+/u;
letters.test('Héllo'); // true
letters.test('你好'); // true
letters.test('12345'); // false
// مطابقة الأحرف الكبيرة عبر جميع الخطوط
const upper = /\p{Lu}+/u;
upper.test('ABC'); // true
upper.test('abc'); // false // مطابقة الرموز التعبيرية
const emoji = /\p{So}/u;
emoji.test('🚀'); // true أكثر خصائص Unicode استخداماً:
\p{L}— أي حرف (جميع الخطوط)\p{Lu}— الأحرف الكبيرة\p{Ll}— الأحرف الصغيرة\p{N}— أي رقم\p{Nd}— الأرقام العشرية\p{P}— علامات الترقيم
10. الأنماط المقروءة عبر تجميع النصوص
تدعم كثير من نكهات التعبيرات المنتظمة (Python وRuby و.NET وPCRE) وضعاً مسهباً أو موسعاً يسمح بالمسافات البيضاء والتعليقات داخل الأنماط. JavaScript لا تملك هذه الميزة.
الحل المعتاد هو تجميع الأنماط من ثوابت نصية مسماة ودمجها مع new RegExp():
// لا توجد علامة x في JavaScript،
// لكن يمكن بناء أنماط مقروءة كثوابت نصية:
const YEAR = '(?<year>\\d{4})';
const SEP = '-';
const MONTH = '(?<month>\\d{2})';
const DAY = '(?<day>\\d{2})';
const datePattern = new RegExp(YEAR + SEP + MONTH + SEP + DAY); كل ثابت يصف ما يطابقه، والنمط النهائي يُقرأ كجملة. يُسهّل هذا الأسلوب أيضاً تأليف أنماط فرعية مشتركة عبر تعريفات متعددة في قاعدة الكود، واختبار كل مكوّن بشكل مستقل.
الصورة الكاملة
هذه التقنيات العشر تغطي جزءاً كبيراً من "لماذا يفشل هذا التعبير المنتظم في الحالات الحدية؟". النظرات للأمام والخلف تتيح لك تأكيد السياق دون استهلاكه. المجموعات المسماة تحافظ على قابلية قراءة الأنماط عبر عمليات إعادة الهيكلة. المحددات الكمية غير الجشعة تمنع المطابقة الزائدة عن الحاجة. هروبات خصائص Unicode تتعامل مع المدخلات التي تتخطى ASCII.
أفضل طريقة لبناء الإتقان هي التجريب مع أنماط حقيقية على بيانات حقيقية. استخدم Regex Tester للتكرار السريع. وإذا احتاج الناتج إلى مقارنة أو تنظيف، تُظهر أداة Text Diff بالضبط ما تغيّر بين عمليتَي التشغيل.