ทำไม RegExp ที่มีการตั้งค่าสถานะส่วนกลางให้ผลลัพธ์ที่ไม่ถูกต้อง


277

ปัญหาของนิพจน์ทั่วไปนี้คืออะไรเมื่อฉันใช้ค่าสถานะโกลบอลและค่าสถานะแบบตายเล็กตัวพิมพ์เล็ก ข้อความค้นหาเป็นข้อมูลที่ผู้ใช้สร้างขึ้น ผลลัพธ์ควรเป็น [จริงจริง]

var query = 'Foo B';
var re = new RegExp(query, 'gi');
var result = [];
result.push(re.test('Foo Bar'));
result.push(re.test('Foo Bar'));
// result will be [true, false]

var reg = /^a$/g;
for(i = 0; i++ < 10;)
   console.log(reg.test("a"));


54
ยินดีต้อนรับสู่หนึ่งในกับดักมากมายของ RegExp ใน JavaScript มีอินเทอร์เฟซที่แย่ที่สุดตัวหนึ่งในการประมวลผล regex ที่ฉันเคยพบมาเต็มไปด้วยผลข้างเคียงที่แปลกประหลาดและคำเตือนที่คลุมเครือ งานทั่วไปส่วนใหญ่ที่คุณต้องการทำกับ regex นั้นยากต่อการสะกดคำ
bobince

XRegExp ดูเหมือนเป็นทางเลือกที่ดี xregexp.com
ประมาณ

ดูคำตอบได้ที่นี่เช่นกัน: stackoverflow.com/questions/604860//
Prestaul

ทางออกหนึ่งถ้าคุณได้รับไปกับมันคือการใช้ regex reอักษรโดยตรงแทนการบันทึกมัน
thdoan

คำตอบ:


350

RegExpวัตถุติดตามlastIndexที่การแข่งขันที่เกิดขึ้นดังนั้นในการแข่งขันที่ตามมาก็จะเริ่มต้นจากดัชนีของใช้ล่าสุดแทนที่จะ 0. ลองดู:

var query = 'Foo B';
var re = new RegExp(query, 'gi');
var result = [];
result.push(re.test('Foo Bar'));

alert(re.lastIndex);

result.push(re.test('Foo Bar'));

หากคุณไม่ต้องการรีเซ็ตlastIndexเป็น 0 ด้วยตนเองหลังการทดสอบทุกครั้งให้ลบการgตั้งค่าสถานะ

นี่คืออัลกอริทึมที่รายละเอียดกำหนด (ส่วน 15.10.6.2):

RegExp.prototype.exec (สตริง)

ดำเนินการจับคู่นิพจน์ปกติของสตริงกับนิพจน์ทั่วไปและส่งคืนวัตถุ Array ที่มีผลลัพธ์ของการจับคู่หรือ null ถ้าสตริงไม่ตรงกับสตริง ToString (สตริง) ถูกค้นหาการเกิดขึ้นของรูปแบบนิพจน์ทั่วไปดังต่อไปนี้:

  1. ให้ S เป็นค่าของ ToString (สตริง)
  2. ให้ความยาวเป็นความยาวของ S
  3. ให้ lastIndex เป็นค่าของคุณสมบัติ lastIndex
  4. ให้ฉันเป็นค่าของ ToInteger (lastIndex)
  5. หากทรัพย์สินส่วนกลางเป็นเท็จให้ฉัน = 0
  6. ถ้า I <0 หรือ I> length ให้ตั้งค่า lastIndex เป็น 0 และ return null
  7. โทรหา [[Match]] โดยให้อาร์กิวเมนต์เป็น S และ i หาก [[ตรงกัน]] กลับล้มเหลวไปที่ขั้นตอนที่ 8 มิฉะนั้นปล่อยให้ r เป็นผลของรัฐและไปที่ขั้นตอนที่ 10
  8. ให้ i = i + 1
  9. ไปที่ขั้นตอนที่ 6
  10. ให้ e เป็นค่า endIndex ของ r
  11. หากคุณสมบัติโกลบอลเป็นจริงให้ตั้งค่า lastIndex เป็น e
  12. ให้ n เป็นความยาวของอาเรย์ของการจับ (นี่เป็นค่าเดียวกับ NCapturingParens ของ 15.10.2.1)
  13. ส่งคืนอาร์เรย์ใหม่ด้วยคุณสมบัติดังต่อไปนี้:
    • คุณสมบัติดัชนีถูกกำหนดเป็นตำแหน่งของสตริงย่อยที่ตรงกันภายในสตริงที่สมบูรณ์ S
    • คุณสมบัติอินพุตถูกตั้งค่าเป็น S
    • ตั้งค่าคุณสมบัติความยาวเป็น n + 1
    • คุณสมบัติ 0 ถูกตั้งค่าเป็นสตริงย่อยที่ตรงกัน (เช่นส่วนของ S ระหว่าง offset i inclusive และ offset e exclusive)
    • สำหรับจำนวนเต็มแต่ละตัวที่ i> 0 และ I ≤ n ให้ตั้งค่าคุณสมบัติชื่อ ToString (i) ไปที่องค์ประกอบ ith ของอาร์เรย์การจับภาพของ r

83
นี่เป็นเหมือน Hitchhiker's Guide to the Galaxy API design ที่นี่ "หลุมพรางที่คุณล้มลงนั้นได้รับการบันทึกไว้อย่างสมบูรณ์แบบในสเปคเป็นเวลาหลายปีถ้าคุณเพียง แต่ใส่ใจที่จะตรวจสอบ"
Retsam

5
การตั้งค่าสถานะที่เหนียวของ Firefox ไม่ได้ทำสิ่งที่คุณบ่งบอกเลย แต่จะทำหน้าที่เหมือนมี ^ ที่จุดเริ่มต้นของนิพจน์ทั่วไปยกเว้นว่า ^ นี้ตรงกับตำแหน่งสตริงปัจจุบัน (lastIndex) แทนที่จะเป็นจุดเริ่มต้นของสตริง คุณกำลังทดสอบว่า regex ตรงกับ "ตรงนี้" แทนที่จะเป็น "ที่ไหนก็ได้หลังจาก lastIndex" ดูลิงค์ที่คุณให้!
Doin

1
คำแถลงการเปิดของคำตอบนี้ไม่ถูกต้อง คุณเน้นขั้นตอนที่ 3 ของข้อมูลจำเพาะที่ไม่ได้พูดอะไร อิทธิพลที่แท้จริงของlastIndexอยู่ในขั้นตอนที่ 5, 6 และ 11 ถ้อยแถลงการเปิดของคุณนั้นเป็นจริงเฉพาะในกรณีที่มีการตั้งค่าธงโกลบอล
Prestaul

@Prestaul ใช่คุณพูดถูกไม่ได้พูดถึงธงโลก อาจเป็นไปได้ (จำไม่ได้ว่าฉันคิดถึงอะไรในตอนนั้น) โดยนัยเนื่องจากวิธีการตั้งคำถาม รู้สึกอิสระที่จะแก้ไขคำตอบหรือลบมันและเชื่อมโยงไปยังคำตอบของคุณ นอกจากนี้ให้ฉันสร้างความมั่นใจให้คุณว่าคุณดีกว่าฉัน สนุก!
Ionuț G. Stan

@ IonuțG.Stan ขอโทษถ้าความคิดเห็นก่อนหน้าของฉันดูเหมือนจะโจมตีนั่นไม่ใช่ความตั้งใจของฉัน ฉันไม่สามารถแก้ไขได้ในตอนนี้ แต่ฉันไม่ได้พยายามตะโกนเพียงเพื่อดึงความสนใจไปที่ประเด็นสำคัญของความคิดเห็นของฉัน ความผิดฉันเอง!
Prestaul

72

คุณกำลังใช้RegExpวัตถุเดียวและดำเนินการหลายครั้ง ในการดำเนินการต่อเนื่องแต่ละครั้งจะดำเนินต่อจากดัชนีการจับคู่ล่าสุด

คุณต้อง "รีเซ็ต" regex เพื่อเริ่มต้นจากจุดเริ่มต้นก่อนการดำเนินการแต่ละครั้ง:

result.push(re.test('Foo Bar'));
re.lastIndex = 0;
result.push(re.test('Foo Bar'));
// result is now [true, true]

ต้องบอกว่ามันอาจอ่านได้ง่ายขึ้นในการสร้างวัตถุ RegExp ใหม่ทุกครั้ง (ค่าใช้จ่ายน้อยมากเนื่องจากมีการแคช RegExp ไว้แล้ว):

result.push((/Foo B/gi).test(stringA));
result.push((/Foo B/gi).test(stringB));

1
หรือเพียงแค่ไม่ใช้gธง
melpomene

36

RegExp.prototype.testอัปเดตlastIndexคุณสมบัตินิพจน์ปกติเพื่อให้การทดสอบแต่ละครั้งจะเริ่มต้นเมื่อการทดสอบครั้งสุดท้ายหยุดลง ฉันขอแนะนำให้ใช้String.prototype.matchเพราะไม่อัปเดตlastIndexคุณสมบัติ:

!!'Foo Bar'.match(re); // -> true
!!'Foo Bar'.match(re); // -> true

หมายเหตุ: !!แปลงเป็นบูลีนจากนั้นกลับบูลีนเพื่อสะท้อนผลลัพธ์

หรือคุณสามารถรีเซ็ตlastIndexคุณสมบัติได้:

result.push(re.test('Foo Bar'));
re.lastIndex = 0;
result.push(re.test('Foo Bar'));

11

การลบการgตั้งค่าสถานะส่วนกลางจะแก้ไขปัญหาของคุณ

var re = new RegExp(query, 'gi');

ควรจะเป็น

var re = new RegExp(query, 'i');

0

คุณต้องตั้งค่า re.lastIndex = 0 เนื่องจากมีการตั้งค่าสถานะ g regex ติดตามการแข่งขันครั้งสุดท้ายดังนั้นการทดสอบจะไม่ไปทดสอบสตริงเดียวกันเพราะคุณต้อง re.lastIndex = 0

var query = 'Foo B';
var re = new RegExp(query, 'gi');
var result = [];
result.push(re.test('Foo Bar'));
re.lastIndex=0;
result.push(re.test('Foo Bar'));

console.log(result)


-1

การใช้แฟล็ก / g บอกให้ทำการค้นหาต่อไปหลังจากการเข้าชม

หากการจับคู่สำเร็จวิธี exec () จะส่งกลับอาร์เรย์และปรับปรุงคุณสมบัติของวัตถุนิพจน์ปกติ

ก่อนค้นหาครั้งแรก:

myRegex.lastIndex
//is 0

หลังจากการค้นหาครั้งแรก

myRegex.lastIndex
//is 8

ลบ g และออกจากการค้นหาหลังจากการโทรแต่ละครั้งเพื่อ exec ()


OP execไม่ได้ใช้
melpomene

-1

ฉันมีฟังก์ชั่น:

function parseDevName(name) {
  var re = /^([^-]+)-([^-]+)-([^-]+)$/g;
  var match = re.exec(name);
  return match.slice(1,4);
}

var rv = parseDevName("BR-H-01");
rv = parseDevName("BR-H-01");

การโทรครั้งแรกใช้งานได้ การโทรครั้งที่สองไม่ได้ การsliceดำเนินการบ่นเกี่ยวกับค่า Null re.lastIndexผมถือว่านี้เป็นเพราะ มันแปลกเพราะฉันคาดหวังใหม่RegExpจะได้รับการจัดสรรใหม่ทุกครั้งที่มีการเรียกใช้ฟังก์ชั่นและไม่แชร์ข้ามการเรียกใช้ฟังก์ชันของฉัน

เมื่อฉันเปลี่ยนเป็น:

var re = new RegExp('^([^-]+)-([^-]+)-([^-]+)$', 'g');

จากนั้นฉันจะไม่ได้รับlastIndexผลกระทบ มันทำงานได้ตามที่ฉันคาดไว้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.