چرا regex.exec() در جاوااسکریپت همیشه همان مقدار را بر نمیگرداند؟


در کنسول کروم یا فایرباگ:

reg = /ab/g
str = "abc"
reg.exec(str)
   ==> ["ab"]
reg.exec(str)
   ==> null
reg.exec(str)
   ==> ["ab"]
reg.exec(str)
   ==> null

آیا exec به نوعی حالتدار است و به آنچه دفعه قبل برگردانده بستگی دارد؟ یا این فقط یک باگ است؟ نمیتوانم کاری کنم که همیشه اتفاق بیفتد. برای مثال، اگر 'str' بالا «abc abc» بود، این اتفاق نمیافتد.

پاسخ شماره ۱

یک شیء RegExp در جاوااسکریپت دارای حالت است.

وقتی که عبارت منظم جهانی است، اگر یک متد روی همان شیء عبارت منظم فراخوانی کنید، از اندیسی که پس از پایان آخرین مطابقت است شروع خواهد شد.

وقتی دیگر هیچ تطابقی پیدا نشود، شاخص بهطور خودکار به 0 بازنشانی میشود.

برای بازنشانی دستی آن، ویژگی lastIndex را تنظیم کنید.

reg.lastIndex = 0;

این میتواند یک ویژگی بسیار مفید باشد. شما میتوانید ارزیابی را در هر نقطهای از رشته شروع کنید اگر بخواهید، یا اگر در یک حلقه هستید، میتوانید آن را بعد از تعداد دلخواهی از مطابقتها متوقف کنید.

در اینجا یک نمایش از رویکرد معمول برای استفاده از regex در یک حلقه ارائه شده است. این روش از این واقعیت استفاده میکند که exec زمانی که تطابق دیگری وجود ندارد، null برمیگرداند و انتساب را به عنوان شرط حلقه انجام میدهد.

var re = /foo_(\d+)/g,
    str = "text foo_123 more text foo_456 foo_789 end text",
    match,
    results = [];

while (match = re.exec(str))
    results.push(+match[1]);

نسخه نمایشی: http://jsfiddle.net/pPW8Y/

اگر از نحوه قرارگیری انتساب خوشتان نمیآید، میتوان حلقه را دوباره کار کرد، مثلاً به این شکل...

var re = /foo_(\d+)/g,
    str = "text foo_123 more text foo_456 foo_789 end text",
    match,
    results = [];

do {
    match = re.exec(str);
    if (match)
        results.push(+match[1]);
} while (match);

نسخه آزمایشی: http://jsfiddle.net/pPW8Y/1/

پاسخ شماره ۲

از مستندات MDN:

اگر عبارت منظم شما از فلگ "g" استفاده میکند، میتوانید از روش exec چندین بار برای یافتن مطابقتهای متوالی در همان رشته استفاده کنید. وقتی این کار را انجام میدهید، جستجو از زیررشته str که توسط ویژگی lastIndex عبارت منظم مشخص شده است شروع میشود (test نیز ویژگی lastIndex را پیش میبرد).

از آنجایی که شما از پرچم g استفاده میکنید، exec از آخرین رشته منطبقشده ادامه میدهد تا به انتها برسد (مقدار null باز میگرداند)، سپس از ابتدا شروع میکند.

شخصاً، ترجیح میدهم راه برعکس را با str.match(reg) بروم

پاسخ شماره ۳

چندین مسابقه

اگر regex شما به فلگ g (مطابقت جهانی) نیاز دارد، شما نیاز خواهید داشت که ایندکس (محل آخرین تطابق) را با استفاده از ویژگی lastIndex بازنشانی کنید.

reg.lastIndex=0;

این به این دلیل است که exec() در هر رخداد متوقف میشود تا بتوانید دوباره روی بخش باقیمانده اجرا کنید. این رفتار همچنین در test() نیز وجود دارد.

اگر عبارت منظم شما از فلگ «g» استفاده میکند، میتوانید از متد exec چندین بار برای یافتن تطابقهای متوالی در همان رشته استفاده کنید. وقتی این کار را انجام میدهید، جستجو از زیررشته str که توسط ویژگی lastIndex عبارت منظم مشخص شده است، شروع میشود (متد test نیز ویژگی lastIndex را پیش میبرد).

مسابقه تنها

وقتی فقط یک تطبیق ممکن وجود دارد، میتوانید به سادگی عبارت منظم خود را با حذف فلگ g بازنویسی کنید، زیرا شاخص بهطور خودکار به 0 بازنشانی خواهد شد.