توسعه فرانت‌اند

Regular Expressions در JavaScript: راهنمای کاربردی

مرز تطبیق، اعداد فارسی، گروه‌ها و state الگو، حدود اعتبارسنجی URL و ایمیل؛ نمونه مستقل شناسه با ورودی‌های معتبر و نامعتبر.

1404/11/29 2 دقیقه مطالعه 1,804 بازدید

یک فرم باید شناسه‌ای مانند TASK-004 را بپذیرد. جستجوی ساده وجود «TASK» کافی نیست؛ رشته‌ای با متن اضافی هم از آن عبور می‌کند. Regular Expression می‌تواند شکل دقیق این شناسه را توصیف کند، به شرط آنکه قرارداد ورودی روشن باشد. Regex برای تطبیق و استخراج متن مناسب است، اما معتبر بودن شکل یک شناسه به معنی وجود آن رکورد یا مجاز بودن استفاده از آن نیست.

پیش از نوشتن الگو، چند ورودی پذیرفته و ردشده تعریف کنید. معلوم کنید فاصله ابتدا و انتها حذف می‌شود، عدد فارسی پذیرفته است یا طول ثابت لازم است. این تصمیم‌ها بخشی از محصول‌اند و از syntax الگو به دست نمی‌آیند. الگویی که بدون قرارداد از اینترنت کپی شده ممکن است بخشی از ورودی معتبر را رد کند یا داده نامناسب را بپذیرد. کوتاه بودن رشته Regex معیار درستی آن نیست.

جستجوی بخشی و تطبیق کامل

راهنمای Regex در MDN روش ساخت literal و RegExp و استفاده در توابع رشته را شرح می‌دهد. یک الگو بدون مرز معمولاً می‌تواند بخشی از رشته را پیدا کند. برای اعتبارسنجی یک قرارداد محدود، مرز شروع و پایان اهمیت دارد. اگر فقط می‌خواهید شناسه‌ای را از متن استخراج کنید، مسئله متفاوت است و نباید همان نتیجه را اعتبار کل متن معرفی کنید.

در نمونه، رشته ابتدا trim می‌شود و سپس شکل کامل بررسی می‌شود. حذف فاصله یک تصمیم صریح است؛ متن اضافه در میانه یا انتها پذیرفته نمی‌شود. اگر فیلد باید فاصله را هم دقیق نگه دارد، این مرحله مناسب نیست. برای log یا سند چندخطی نیز معنی مرزها با flagهای مربوط می‌تواند تغییر کند. هدف را ابتدا بنویسید تا انتخاب flag به رفتار مورد انتظار وابسته باشد.

کلاس کاراکتر همه زبان‌ها را پوشش نمی‌دهد

مرجع Character Classes توضیح می‌دهد که بازه‌ای مانند a تا z با همه حروف جهان معادل نیست. کلاس digit معمول در JavaScript به رقم‌های ASCII اشاره دارد و word نیز نباید معادل کامل واژه فارسی فرض شود. نقطه هم در حالت پیش‌فرض line terminatorها را پوشش نمی‌دهد؛ flag مربوط می‌تواند این رفتار را تغییر دهد. عبارت «هر کاراکتر» بدون این دامنه ناقص است.

برای ورودی فارسی، قواعد یکسان‌سازی و پذیرش باید جدا مشخص شوند. عدد فارسی و عربی می‌توانند به ASCII تبدیل شوند، اگر همین قرارداد لازم است. تبدیل همه نویسه‌های مشابه بدون شناخت معنی، ممکن است داده را تغییر دهد. propertyهای Unicode برای بعضی نیازها مفیدند، ولی انتخاب script یا category باید با دامنه داده هماهنگ باشد. نام و نشانی افراد را با یک بازه محدود بی‌دلیل رد نکنید.

Quantifier مقدار تکرار را بیان می‌کند

مرجع Quantifiers تفاوت صفر یا بیشتر، یک یا بیشتر و محدوده مشخص را توضیح می‌دهد. در شناسه آموزشی، سه رقم لازم است، بنابراین تکرار دقیق استفاده می‌شود. برای متن آزاد، تکرار نامحدود باید با احتیاط و شناخت محدودیت ورودی انتخاب شود. وجود quantifier فقط شکل را بیان می‌کند و محدودیت مصرف حافظه یا طول درخواست را در لایه برنامه تعیین نمی‌کند.

رفتار greedy و lazy در استخراج می‌تواند محدوده نتیجه را عوض کند. انتخاب lazy به‌تنهایی مشکل parsing ساختار پیچیده را حل نمی‌کند. برای سندی که nesting و قواعد escaping دارد، parser اختصاصی اغلب قرارداد روشن‌تری می‌دهد. Regex را برای بخشی که واقعاً الگوی متن است نگه دارید. اگر توضیح الگو از خود مسئله پیچیده‌تر شده، شاید ابزار انتخابی مناسب آن دامنه نباشد.

نمونه کامل شناسه آموزشی

کد بعدی را با نام regex-demo.js و UTF-8 ذخیره کنید. با Node.js جدید دارای named capture group، مانند نسخه 24، فرمان node regex-demo.js را اجرا کنید. بسته خارجی، API یا فایل دیگری لازم نیست. ورودی‌ها ثابت و آموزشی‌اند. این مثال شکل شناسه TASK با سه رقم را می‌پذیرد؛ درباره وجود وظیفه در سامانه یا معتبر بودن شماره تلفن و نشانی ایمیل تصمیم نمی‌گیرد.

function normalizeDigits(value) {
  return value.replace(/[۰-۹]/g, digit => String(digit.charCodeAt(0) - '۰'.charCodeAt(0)))
    .replace(/[٠-٩]/g, digit => String(digit.charCodeAt(0) - '٠'.charCodeAt(0)));
}
const TASK_ID = /^TASK-(?<number>[0-9]{3})$/u;
function parseTaskId(value) {
  if (typeof value !== 'string' || value.length > 40) return null;
  const normalized = normalizeDigits(value.trim());
  const match = TASK_ID.exec(normalized);
  return match ? { id: normalized, number: match.groups.number } : null;
}
for (const input of ['TASK-004', ' TASK-۰۰۴ ', 'TASK-04', 'TASK-004 extra']) {
  console.log(JSON.stringify(input), parseTaskId(input));
}

تابع normalizeDigits رقم‌های فارسی و عربی را تبدیل می‌کند و parseTaskId طول ورودی را پیش از تطبیق محدود می‌کند. عدد استخراج‌شده string می‌ماند تا صفرهای آغازین حذف نشوند. نتیجه نمونه برای TASK-004 معتبر، برای نسخه فارسی همان شکل معتبر و برای شناسه دورقمی یا متن اضافه نامعتبر است. ورودی غیرstring نیز پذیرفته نمی‌شود. این محدودیت‌ها از قرارداد همان شناسه می‌آیند.

گروه‌بندی و استخراج را روشن نگه دارید

مرجع Groups تفاوت گروه capture و گروه صرفاً ساختاری را شرح می‌دهد. گروه نام‌دار number در نمونه باعث می‌شود بخش استخراج‌شده معنی داشته باشد. وابستگی به شماره گروه می‌تواند بعد از افزودن پرانتز تازه مبهم شود. اگر قرار نیست بخش را استخراج کنید، capture اضافی لازم نیست. نام گروه باید نقش داده را توضیح دهد، نه موقعیت اتفاقی آن در الگو را.

Backreference می‌تواند به نتیجه capture وابسته باشد و بعضی قراردادهای متن را بیان کند، اما اضافه کردن آن بدون نیاز خوانایی را کم می‌کند. خروجی استخراج هم باید پیش از مصرف بررسی شود. اگر قرار است شناسه در URL یا query استفاده شود، قواعد همان مقصد لازم‌اند. رشته تطبیق‌یافته را نباید بی‌دلیل HTML خام یا فرمان shell تلقی کنید. اعتبار شکل، مجوز مصرف در همه زمینه‌ها نیست.

Flagهای g و y وضعیت دارند

مرجع test اثر lastIndex را در الگوهای دارای g یا y توضیح می‌دهد. صدا زدن مکرر یک RegExp stateful می‌تواند نتیجه‌های متفاوتی داشته باشد، حتی وقتی ورودی مشابه است. برای بررسی مستقل شکل یک فیلد، معمولاً نیازی به g نیست. اگر استخراج چند مورد هدف است، وضعیت و شیوه پیمایش باید آگاهانه مدیریت شوند.

در کد نمونه، الگو g ندارد و هر ورودی مستقل بررسی می‌شود. این انتخاب از هدف اعتبارسنجی می‌آید. استفاده از همان شیء global در چند جای برنامه می‌تواند خطایی بسازد که فقط در ترتیب خاصی رخ می‌دهد. نتیجه بولی را بدون شناخت state تفسیر نکنید. برای منطق تکرارشونده، قرارداد تابع و ورودی باید امکان پیش‌بینی نتیجه را حفظ کند.

ایمیل و URL دامنه پیچیده‌تری دارند

یک Regex کوتاه ممکن است خطای آشکار ایمیل را پیدا کند، اما دسترسی به صندوق یا همه شکل‌های مجاز را اثبات نمی‌کند. تأیید مالکیت به فرایند دیگری نیاز دارد. برای URL، API مربوط به URL parsing ساختاری را فراهم می‌کند. بعد از parse، protocol و مقصدهای مجاز باید با نیاز برنامه بررسی شوند؛ قابل parse بودن یک نشانی به معنی امن بودن هر استفاده از آن نیست.

شماره تلفن و کد پستی نیز به قرارداد کشور، شکل نمایش و داده معتبر وابسته‌اند. مثال آموزشی را با ادعای پوشش کامل سامانه‌های واقعی منتشر نکنید. Regex می‌تواند بخش شکل را بررسی کند و قواعد دامنه در تابع جدا بمانند. راهنمای اعتبارسنجی API تفاوت داده قابل parse و مدل پذیرفته‌شده را بیشتر توضیح می‌دهد. سمت client جای بررسی سمت سرور را نمی‌گیرد.

هزینه و ورودی بی‌اعتماد

بعضی الگوهای مبهم با تکرارهای تو در تو روی ورودی طولانی کار زیادی انجام می‌دهند. محدود کردن طول ورودی، ساده کردن الگو و انتخاب parser مناسب می‌تواند دامنه ریسک را کاهش دهد. ورودی کاربر را بدون قرارداد به خود syntax الگو تبدیل نکنید؛ ممکن است به جای جستجوی literal، معنی متفاوتی بسازد. اگر فقط وجود یک عبارت ثابت لازم است، روش رشته‌ای ساده می‌تواند کافی باشد.

قبل از قبول یک الگو، نمونه مرزی و ردشده را هم در توضیح بیاورید. نام تابع باید بگوید چه قرارداد محدودی را بررسی می‌کند. راهنمای خوانایی JavaScript به ثبت همین معنی کمک می‌کند. تغییر الگو بعد از تغییر نیاز باید همراه با مرور مثال‌ها باشد؛ ورودی قدیمی شاید همچنان معتبر باشد یا سیاست پذیرش تازه‌ای لازم شود.

تحویل قابل فهم

هدف، flagها، شیوه یکسان‌سازی و خروجی را کنار کد بنویسید. مشخص کنید تابع شکل را بررسی می‌کند یا داده‌ای را استخراج می‌کند. برای کاربرد UI، پیام خطا باید راه اصلاح بدهد؛ نتیجه false به‌تنهایی متن مناسبی برای مخاطب نیست. راهنمای جستجو درباره عبارت، نتیجه و حفظ ورودی توضیح دارد. الگوی متن بخشی از قرارداد تجربه است.

Regex زمانی مفید است که مسئله محدود و معنی خروجی روشن باشد. syntax کوتاه می‌تواند کار چند مرحله را خلاصه کند، اما نباید دامنه ادعا را بزرگ کند. از الگو برای تطبیق قابل توضیح استفاده کنید و parsing، مجوز و اعتبار دامنه را در جای مناسب نگه دارید. این تفکیک باعث می‌شود کد با داده واقعی قابل نگهداری باشد و تغییر نیاز به یک رشته ناشناخته و شکننده وابسته نماند.

واکنش به این مقاله

اشتراک‌گذاری این مقاله:
دیدگاه‌ها 0
۰ / ۲۰۰۰

هنوز دیدگاهی ثبت نشده

اولین نفری باشید که نظر می‌دهد!