Tại sao một RegExp với cờ toàn cầu cho kết quả sai?


277

Vấn đề với biểu thức chính quy này là gì khi tôi sử dụng cờ toàn cầu và cờ không phân biệt chữ hoa chữ thường? Truy vấn là một đầu vào do người dùng tạo ra. Kết quả phải là [đúng, đúng].

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
Chào mừng bạn đến với một trong nhiều bẫy của RegExp trong JavaScript. Nó có một trong những giao diện tồi tệ nhất để xử lý regex mà tôi từng gặp, với đầy đủ các tác dụng phụ kỳ lạ và cảnh báo khó hiểu. Hầu hết các tác vụ phổ biến bạn thường muốn thực hiện với regex đều khó đánh vần đúng.
bobince

XRegExp trông giống như một sự thay thế tốt. xregapi.com
khoảng

Xem câu trả lời ở đây là tốt: stackoverflow.com/questions/604860/ từ
Prestaul

Một giải pháp, nếu bạn có thể thoát khỏi nó, là sử dụng trực tiếp regex bằng chữ thay vì lưu nó vào re.
thdoan

Câu trả lời:


350

Đối RegExptượng theo dõi lastIndexnơi xảy ra trận đấu, vì vậy trong các trận đấu tiếp theo, nó sẽ bắt đầu từ chỉ mục được sử dụng cuối cùng, thay vì 0. Hãy xem:

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'));

Nếu bạn không muốn đặt lại thủ công lastIndex về 0 sau mỗi lần kiểm tra, chỉ cần xóa gcờ.

Đây là thuật toán mà thông số kỹ thuật ra lệnh (phần 15.10.6.2):

RegExp.prototype.exec (chuỗi)

Thực hiện khớp chuỗi biểu thức chính quy với biểu thức chính quy và trả về một đối tượng Array chứa kết quả của kết quả khớp hoặc null nếu chuỗi không khớp Chuỗi ToString (chuỗi) được tìm kiếm cho sự xuất hiện của mẫu biểu thức chính quy như sau:

  1. Đặt S là giá trị của ToString (chuỗi).
  2. Đặt chiều dài là chiều dài của S.
  3. Đặt Last Index là giá trị của thuộc tính Last Index.
  4. Đặt i là giá trị của ToInteger (last Index).
  5. Nếu thuộc tính toàn cầu là sai, hãy để i = 0.
  6. Nếu tôi <0 hoặc I> độ dài thì đặt Last Index thành 0 và trả về null.
  7. Gọi [[Match]], cung cấp cho nó các đối số S và i. Nếu [[Trận đấu]] trở lại thất bại, hãy chuyển đến bước 8; mặt khác, hãy để r là kết quả trạng thái của nó và chuyển sang bước 10.
  8. Đặt i = i + 1.
  9. Chuyển đến bước 6.
  10. Đặt e là giá trị end Index của r.
  11. Nếu thuộc tính toàn cầu là true, hãy đặt Last Index thành e.
  12. Gọi n là độ dài của mảng bắt giữ của r. (Đây là giá trị tương tự như NCapturingParens của 15.10.2.1.)
  13. Trả về một mảng mới với các thuộc tính sau:
    • Thuộc tính chỉ mục được đặt ở vị trí của chuỗi con phù hợp trong chuỗi hoàn chỉnh S.
    • Thuộc tính đầu vào được đặt thành S.
    • Thuộc tính độ dài được đặt thành n + 1.
    • Thuộc tính 0 được đặt thành chuỗi con phù hợp (nghĩa là phần S giữa offset i đã bao gồm và offset e độc ​​quyền).
    • Đối với mỗi số nguyên i sao cho I> 0 và I, n, đặt thuộc tính có tên ToString (i) thành phần tử thứ i của mảng bắt giữ của r.

83
Điều này giống như Hướng dẫn của Hitchhiker về thiết kế API Galaxy ở đây. "Cạm bẫy mà bạn rơi vào đã được ghi lại một cách hoàn hảo trong thông số kỹ thuật trong vài năm, nếu bạn chỉ bận tâm kiểm tra"
Retsam

5
Cờ dính của Firefox không làm những gì bạn ngụ ý cả. Thay vào đó, nó hoạt động như thể có một ^ khi bắt đầu biểu thức chính quy, NGOẠI TRỪ rằng điều này ^ phù hợp với vị trí chuỗi hiện tại (last Index) thay vì bắt đầu chuỗi. Bạn đang kiểm tra hiệu quả nếu regex khớp với "ngay tại đây" thay vì "bất cứ nơi nào sau lần cuối". Xem liên kết bạn cung cấp!
Doin

1
Tuyên bố mở đầu của câu trả lời này chỉ là không chính xác. Bạn nhấn mạnh bước 3 của thông số không nói gì. Ảnh hưởng thực tế của lastIndexcác bước 5, 6 và 11. Tuyên bố mở đầu của bạn chỉ đúng NẾU CẦU LÔNG TOÀN CẦU.
Prestaul

@Prestaul có, bạn đúng là nó không đề cập đến cờ toàn cầu. Có lẽ (không thể nhớ những gì tôi nghĩ lúc đó) ẩn ý do cách câu hỏi được đóng khung. Hãy chỉnh sửa câu trả lời hoặc xóa nó và liên kết với câu trả lời của bạn. Ngoài ra, hãy để tôi trấn an bạn rằng bạn tốt hơn tôi. Thưởng thức!
Ionuț G. Stan

@ IonuțG.Stan, xin lỗi nếu nhận xét trước đây của tôi có vẻ tấn công, đó không phải là ý định của tôi. Tôi không thể chỉnh sửa nó vào thời điểm này, nhưng tôi đã không cố hét lên, chỉ để thu hút sự chú ý đến điểm quan trọng trong nhận xét của tôi. Lỗi của tôi!
Prestaul

72

Bạn đang sử dụng một RegExp đối tượng và thực thi nó nhiều lần. Trên mỗi lần thực hiện liên tiếp, nó tiếp tục từ chỉ số khớp cuối cùng.

Bạn cần "thiết lập lại" regex để bắt đầu lại từ đầu trước mỗi lần thực hiện:

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

Đã nói rằng có thể dễ đọc hơn để tạo một đối tượng RegExp mới mỗi lần (chi phí là tối thiểu vì RegExp được lưu trong bộ nhớ cache):

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

1
Hoặc đơn giản là không sử dụng gcờ.
melpomene

36

RegExp.prototype.testcập nhật thuộc tính của biểu thức chính quy lastIndexđể mỗi bài kiểm tra sẽ bắt đầu khi điểm cuối cùng dừng lại. Tôi khuyên bạn nên sử dụng String.prototype.matchvì nó không cập nhật thuộc lastIndextính:

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

Lưu ý: !!chuyển đổi nó thành boolean và sau đó đảo ngược boolean để nó phản ánh kết quả.

Ngoài ra, bạn chỉ có thể đặt lại lastIndextài sản:

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

11

Xóa gcờ toàn cầu sẽ khắc phục vấn đề của bạn.

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

Nên là

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

0

Bạn cần đặt re.lastIndex = 0 vì với g cờ regex, hãy theo dõi trận đấu cuối cùng xảy ra, do đó, kiểm tra sẽ không đi kiểm tra cùng một chuỗi, vì bạn cần phải thực hiện 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

Sử dụng cờ / g bảo nó tiếp tục tìm kiếm sau khi nhấn.

Nếu khớp thành công, phương thức exec () trả về một mảng và cập nhật các thuộc tính của đối tượng biểu thức chính quy.

Trước khi tìm kiếm đầu tiên của bạn:

myRegex.lastIndex
//is 0

Sau lần tìm kiếm đầu tiên

myRegex.lastIndex
//is 8

Xóa g và nó thoát khỏi tìm kiếm sau mỗi lệnh gọi tới exec ().


OP không sử dụng exec.
melpomene

-1

Tôi đã có chức năng:

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");

Cuộc gọi đầu tiên hoạt động. Cuộc gọi thứ hai không. Các slicehoạt động phàn nàn về một giá trị null. Tôi cho rằng điều này là do re.lastIndex. Điều này là lạ bởi vì tôi mong đợi một cái mớiRegExp sẽ được phân bổ mỗi khi hàm được gọi và không được chia sẻ qua nhiều lần gọi hàm của tôi.

Khi tôi đổi nó thành:

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

Sau đó, tôi không nhận được lastIndexhiệu ứng nắm giữ. Nó hoạt động như tôi mong đợi nó.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.