2015-08-26 3 views
2

У меня есть следующая ситуация, использующая встроенную функцию JS FILTER, как описано.Почему функция родного JS FILTER меняет свой результат

var array = ['hello', 'you', 'hello', 'you', 'hello', 'you', 'hello', 'you', 'hello', 'you', 'hello', 'you', 'hello', 'you', 'hello']; 

var regex = new RegExp("hello", "gi"); 

function allMatches(item, index, array){ 
    return this.test(item); 
} 

Теперь запустите его, как это ...

array.filter(allMatches, regex).length; 
=> 7 // should be 8. 

Примечание: все 8 элементов, которые проходят в AllMatches функция возвращает истину, как они должны, однако массив отсутствует один элемент.

Теперь давайте исправьте allMatches, как описано ниже.

var array = ['hello', 'you', 'hello', 'you', 'hello', 'you', 'hello', 'you', 'hello', 'you', 'hello', 'you', 'hello', 'you', 'hello']; 

// NO LONGER USE THIS var regex = new RegExp("hello", "gi"); 

function allMatches(item, index, array){ 
    return /hello/gi.test(item); 
} 

Теперь работать, как прежде ...

array.filter(allMatches).length; 
=> 8 // as it should be. 

Обратите внимание на следующие пункты ВСЕ примеры ...

  1. Каждая итерация ЭТА является регулярное выражение, как это должно быть.
  2. Каждая итерация, которая должна быть ИСТИНА верно, то есть все 8.
  3. ЭТО всегда правильное регулярное выражение.

Я не ищу, как это сделать, поскольку я знаю способ сделать это. Я хотел бы получить глубокое понимание того, почему это происходит, чтобы я мог избежать проблемы в будущем.

Вот несколько попыток, используя следующую функцию

var regex = new RegExp("hello", "gi"); // as before 

function allMatches(item, index, array){ 
    return this.test(item); 
} 

array.filter(allMatches, regex).length; 
=> 7 // again, NOT correct. 

array.filter(allMatches, /hello/gi).length; 
=> 8 // correct. Passing a regex litteral. 

array.filter(allMatches, new RegExp("hello", "gi")).length; 
=> 8 // correct. Passing the same regexp from 

Здесь же нерабочий пример, но с ссылочным регулярным выражением буквальным. var nonConstructorRegex =/hello/gi;

array.filter(allMatches, nonConstructorRegex).length; 
=> 7 // NOT correct. 

Примечание: Я думал, что это что-то делать с проходную ссылкой на регулярное выражение, но с использованием регулярных выражений ссылки буквенные работ, где новая переменная RegExp построен из конструктора не делает. См. Первый пункт выше.

ОБНОВЛЕНИЕ

Когда результат не является правильным, он рассчитывает первый ПРИВЕТ как ложь, которая должна быть TRUE,.

Это швы происходит, когда значение регулярного выражения передается как ссылка. Когда регулярное выражение, построенное или литеральное, передается без ссылки.

Могло ли это просто время?

UPDATE

После изучения ссылки MDN от ответа, я понимаю, почему.

Примеры Нахождение последовательных матчей

Если регулярное выражение использует «г» флаг, вы можете использовать метод Exec() несколько раз, чтобы найти последовательные матчи в одной и той же строке. Когда вы это сделаете, поиск начинается с подстроки str, заданной свойством lastIndex регулярного выражения (test() также увеличивает свойство lastIndex). Например, предположит, что у вас есть этот скрипт:

var myRe = /ab*/g; 
var str = 'abbcdefabh'; 
var myArray; 

while ((myArray = myRe.exec(str)) !== null) { 
    var msg = 'Found ' + myArray[0] + '. '; 
    msg += 'Next match starts at ' + myRe.lastIndex; 
    console.log(msg); 
} 

Как проверить эту идею с моим примером ...

var array = ['hello', 'hello', 'hello', 'hello']; 
var regex = new RegExp("hello", "gi"); 

function allMatches(item, index, array){ 
    console.log(this.lastIndex) 
return this.test(item); 
} 

array.filter(allMatches, regex).length; 
=> 2. 

Каждой итерация будет console.log накопленного положения .lastIndex.

Так что первый пункт приведет к .lastIndex из как в положении 5 строки «привет».

Следующая привет ударит, но .lastIndex будет в положении 5. Функция .test() будет проверить, если строка соответствует регулярное выражение, но в положении . Это приведет к и таким образом сбросить .lastIndex 0.

Третья итерация начнется в и проверить, что привет совпадает с регулярным выражением, которое было бы.

Четвертая итерация затем ударит, но .lastIndex будет снова установлен в положение , и поэтому мы продолжим делать это до конца массива, в этом случае это так.

Таким образом ...

ЭТО БЫЛО ПОЛКОВНИК Горчица, В раздевалке, с "г" ФЛАГ !!!

+0

Корень вашей проблемы - это флаг «g». Почему вы включаете его? – Pointy

+1

Я получаю 8 вместо 7 для вашего первого примера: http://jsfiddle.net/trfxgcj6/ –

+0

Также, когда я пробую первый образец, он отлично работает; первый результат имеет длину 8, а не 7. – Pointy

ответ

3

Это не имеет никакого отношения к .filter().

Когда вы добавляете флаг «g» в регулярное выражение, каждый вызов .test() выполняет поиск исходной строки, и если совпадение найдено, оно установит значение свойства .lastIndex объекта RegExp к индексу в исходную строку, где должен начаться следующий поиск.

Если вы используете литерал регулярных выражений в функции фильтра, это не имеет значения, поскольку каждый вызов функции создает новый экземпляр RegExp. Однако при повторном использовании регулярного выражения при последовательных вызовах значение .lastIndex будет иметь эффект.

Образец, который вы опубликовали, с массивом строк, которые поочередно совпадают и не совпадают, не проявит никакой очевидной проблемы. Однако, если бы у вас было две строки "hello", то это было бы, потому что после сопоставления первой пары значение .lastIndex было бы равно 5, поэтому, когда вызывается .test(), следующий поиск начнется в конце строка и ошибка.

Суть: избавиться от флага «g».

+0

У вас есть ссылка, которую я мог бы прочитать относительно этого подробного поведения? Спасибо за объяснение. Я удалил флаг «g», и он работает 4.0. – SoEzPz

+0

@SoEzPz [Здесь находится страница MDN для функции '.exec()', которая имеет такое же поведение.] (Https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ RegExp/exec) Менее распространено желание использовать поведение '.lastIndex' с' .test() '. – Pointy