Javascript-форум (https://javascript.ru/forum/)
-   Общие вопросы Javascript (https://javascript.ru/forum/misc/)
-   -   Регулярное выражение в замыкании, проблема в IE (https://javascript.ru/forum/misc/2324-regulyarnoe-vyrazhenie-v-zamykanii-problema-v-ie.html)

Octane 08.12.2008 16:30

Регулярное выражение в замыкании, проблема в IE
 
Написал функцию, для преобразования CSS-свойст:
border-bottom-color → borderBottomColor
background-image → backgroundImage
z-index → zIndex
и т.д.

Вот сама функция:
function change(prop) {
	var expr = /-([a-z])/g;
	return prop == 'float' ? 'styleFloat' : expr.test(prop) ? prop.replace(expr, function () {
		return arguments[1].toUpperCase();
	}) : prop;
}

Используем:
alert([change('background-image'), change('background-image')]);

Результат:
backgroundImage,backgroundImage

Все работает нормально :)

Начинаем извращаться над функцией, занесем инициализацию переменной в замыкание, чтобы каждый раз при вызове функции «change» не создавать новый объект «RegExp»:
var change = function () {
	var expr = /-([a-z])/g;
	return function(prop) {
		return prop == 'float' ? 'styleFloat' : expr.test(prop) ? prop.replace(expr, function () {
			return arguments[1].toUpperCase();
		}) : prop;
	};
}();

Вызываем с теме же параметрами и получаем результат:
backgroundImage,background-image

Второй раз преобразования не произошло.

Это еще не конец фокуса. Вызовем функцию 3 раза:
alert([change('background-image'), change('background-image'), change('background-image')]);

Получим:
backgroundImage,background-image,backgroundImage

Вызовем 4 раза:
alert([change('background-image'), change('background-image'), change('background-image'), change('background-image')]);

Получим:
backgroundImage,background-image,backgroundImage,background-image

и т.д.

Такое поведение наблюдается только в IE любой версии, даже 8b2.
Почему так происходит? Стоит избегать попадания регулярных выражений в замыкание?

Kolyaj 08.12.2008 17:22

Цитата:

Сообщение от Octane
styleFloat

Это в ИЕ styleFloat, в остальных cssFloat.

Цитата:

Сообщение от Octane
/\-([a-z]){1}/g

Минус экранировать необязательно, {1} вообще непонятно зачем.

А по теме: что говорит для разных строк, для нескольких черточек? Тестировать лень :)

vk65535 08.12.2008 17:37

Проблема в том, что в объекте регэкспа хранится смещение до следующего глобального поиска.
Я эту проблему решил так:
RegExp.prototype.reset = function() {
	this.lastIndex = 0;
	return this;
};

и всякий раз, когда нужно работать с глобальным регэкспом:
...
expr.reset().test(prop) ? prop.replace( ...
...

Octane 08.12.2008 17:39

Цитата:

Сообщение от Kolyaj (Сообщение 9359)
Это в ИЕ styleFloat, в остальных cssFloat.

Да я знаю. Эта функция будет использоваться для преобразования имен CSS-свойств в IE, чтобы получать значения «currentStyle», т.к. в других браузерах в метод «getPropertyValue» нужно отправлять значения вида: «float», «border-bottom-color», «background-image» и т.д.

Цитата:

Сообщение от Kolyaj (Сообщение 9359)
Минус экранировать необязательно, {1} вообще непонятно зачем.

Спасибо, поправил
var expr = /-([a-z])/g;

глюк остался
Цитата:

Сообщение от Kolyaj (Сообщение 9359)
А по теме: что говорит для разных строк, для нескольких черточек? Тестировать лень :)

В смысле не только для «background-image»? Ведет себя так же с любым параметром.

vk65535, спасибо за решение.
Не встречал в литературе описания подобных вещей. И странно почему такое поведение только в IE :) и почему именно через раз срабатывает и только в замыкании? Хочу понять, баг это или так должно быть?

vk65535 08.12.2008 19:02

Дело даже не в осле - если есть глобальный флаг, смещение до начала следующего поиска хранится в самом объекте регэкспа. А поскольку в замыкании он используется один и тот же, это смещение всякий раз остается таким, каким оно стало в предыдущем вызове функции.
Если прогнать небольшой тест:
(function() {
	var re = /\S+/g;
	return function(a) {
		var _ = re.exec(a);
		if (_) alert('"' + a + '" matches: "' + _[0] + '"');
		else alert('"' + a + '" not matched');
		return arguments.callee;
	}
})()('aaa bbb ccc')('000 111 222')('--- +++ ===')('zzz xxx yyy');

То станет ясно, что поиск каждый раз продолжается с той позиции, где был завершен предыдущий. При этом, когда встречается конец строки, регэксп фэйлит совпадение, а следующее уже начинает опять с начала. Но различие между ослом и лисой все-таки есть: в случае
function() {
  var re = /.../g;
}

осел создаст новый инстанс регэкспа, а хитрая лиса - использует имеющийся, видимо экономит на компиляции.

Zibba 08.12.2008 19:24

Встречал упоминания об этом в литературе. Подробнее можно почитать
тут: http://www.unix.com.ua/orelly/webpro...pt/ch10_03.htm
и тут: http://www.unix.com.ua/orelly/webpro...pt/ch10_02.htm (в описании метода match() )

Octane 08.12.2008 19:55

Получается, если каждый раз нужен независимый поиск, нужно либо создавать новый объект «RegExp» или сбрасывать «lastIndex».

vk65535 08.12.2008 20:07

Только для функций test и exec.
Странно, в http://www.unix.com.ua/orelly/webpro...pt/ch10_02.htm написано, что match создает аналогичную ситуацию, но тесты этого не подтвердили.

Zibba 08.12.2008 20:43

Нет вы наверное что то перепутали, у match() если она вызывается без глобального флага g, возвращаемый массив имеет помимо свойства length еще два свойства: index (а не lastIndex) содержащее номер позиции символа внутри строки, с которого начинается соответствие, и input, являющееся копией строки, в которой выполняется поиск.
То есть для регулярного выражения r, в котором не установлен флаг g, вызов s.match(r) возвращает то же значение, что и r.exec(s) (в отличие от match(), exec() возвращает массив, структура которого не зависит от наличия в регулярном выражении флага g). Когда же exec() вызывается для регулярного выражения с флагом g, метод устанавливает свойство lastIndex объекта регулярного выражения равным номеру позиции символа, следующего непосредственно за найденной подстрокой. Когда метод exec() вызывается для того же регулярного выражения второй раз, то он начинает поиск с символа, позиции которого хранится в lastIndex. Т.е. выходит что если мы завершаем поиск до того, как нашли последнее соответствие в одной строке и начинаем поиск в другой строке с тем же объектом RegExp, то нужно скинуть свойство lastIndex в 0.
P.S. Методы search(), replace() и match() не задействуют свойство lastIndex, поэтому Ваши тесты этого и не подтвердили.


Часовой пояс GMT +3, время: 21:26.