Код jQuery слишком многословный, хотел бы указать указатели на то, как сделать его короче
У меня есть код jQuery, на который я бы хотел посмотреть обзоры и указатели на то, как подсчитывать и сокращать количество строк.
$('#p1').click(function() {
$('#list').fadeOut(450);
$('#q1').delay(600).fadeIn(450)
});
$('#p2').click(function() {
$('#list').fadeOut(450);
$('#q2').delay(600).fadeIn(450)
});
$('#p3').click(function() {
$('#list').fadeOut(450);
$('#q3').delay(600).fadeIn(450)
});
$('#p4').click(function() {
$('#list').fadeOut(450);
$('#q4').delay(600).fadeIn(450)
});
...
$('#p12').click(function() {
$('#list').fadeOut(450);
$('#q12').delay(600).fadeIn(450)
});
$('#p13').click(function() {
$('#list').fadeOut(450);
$('#q13').delay(600).fadeIn(450)
});
Можно ли оптимизировать этот код? Или, по крайней мере, сделать менее многословным?
Ответы
Ответ 1
Вы можете использовать цикл for
, но вы должны убедиться, что значение счетчика циклов попадает в правильную область для обработчика событий click
:
var clickHandler = function(k) {
return function() {
$('#list').fadeOut(450);
$('#q' + k).delay(600).fadeIn(450);
};
};
for (var i = 1; i < 14; ++i) {
$('#p' + i).click(clickHandler(i));
}
В противном случае delay
и fadeIn
будут применяться только к элементу #q13
, так как фактический счетчик (с его конечным значением 13) будет закрыт.
РЕДАКТИРОВАТЬ. Поскольку здесь было довольно много ответов, я попытаюсь более точно объяснить, что происходит в этом коде, поскольку это кажется довольно запутанным.
"Естественное" решение с инжектированием обработчика щелчка непосредственно в цикл будет следующим:
for(var i = 1; i < 14; i++) {
$('#p'+i).click(function() {
$('#list').fadeOut(450);
$('#q'+i).delay(600).fadeIn(450)
});
}
Но это совсем не эквивалентно расширенной форме, в которой перечислены все 13 вариантов один за другим. Проблема в том, что, хотя здесь создано 13 функций, все они закрыты по той же переменной i
, значение которой изменяется. Он, наконец, достигает значения 13
, и цикл завершается.
Спустя некоторое время вызываются функции, прикрепленные к элементам #p1
... #p13
(при нажатии одного из этих элементов), и они используют это окончательное значение i
. Это приводит только к анимации #q13
.
Что нужно сделать, так это сделать что-то, называемое лямбда-лифтинг, и устранить свободную переменную i
, значение которой будет случайно изменено. Общей методикой является предоставление "factory функции", которая принимает значение для нашей переменной и выводит фактическую функцию, которую мы будем использовать в качестве обработчика события:
var clickHandler = function(k) {
return function() {
$('#list').fadeOut(450);
$('#q' + k).delay(600).fadeIn(450);
};
};
Поскольку область действия параметра k
локальна для clickHandler
, каждый вызов clickHandler
получает другую переменную k
. Функция, возвращаемая из clickHandler
, поэтому закрыта для разных переменных, которые, в свою очередь, могут иметь разные значения. Это именно то, что нам нужно. Затем мы можем вызывать clickHandler
из нашего цикла, передавая ему значение счетчика:
for (var i = 1; i < 14; ++i) {
$('#p' + i).click(clickHandler(i));
}
Надеюсь, это немного изменит ситуацию.
РЕДАКТИРОВАТЬ. Как отметил Есалия в комментариях, для достижения аналогичного эффекта можно использовать jQuery.each
:
$.each(new Array(13), function(idx) {
$('#p' + (idx + 1)).click(function() {
$('#list').fadeOut(450);
$('#q' + idx).delay(600).fadeIn(450);
});
});
Это, вероятно, решение по выбору, если вы уже знаете о проблеме закрытия/охвата, которую я попытался описать выше.
Ответ 2
Идеальное решение IMO, вопреки принятому ответу, не должно основывать ваш код на отношениях между значениями id
, а основывать его на отношениях в DOM. jQuery предоставляет вам метод index
, который позволяет вам видеть, где находится ваш элемент по отношению к его братьям и сестрам. Затем вы можете использовать это значение, чтобы выбрать соответствующий другой элемент для показа.
Это, конечно, требует, чтобы вы структурировали свой HTML семантическим способом. Например:
<div id="list">
<a href="#">Link 1</a>
<a href="#">Link 2</a>
<a href="#">Link 3</a>
<a href="#">Link 4</a>
<a href="#">Link 5</a>
<a href="#">Link 6</a>
<a href="#">Link 7</a>
<a href="#">Link 8</a>
</div>
<div id="questions"> <!-- perhaps this is what q stands for... -->
<div>1 ...</div>
<div>2 ...</div>
<div>3 ...</div>
<div>4 ...</div>
<div>5 ...</div>
<div>6 ...</div>
<div>7 ...</div>
<div>8 ...</div>
</div>
Первая ссылка относится к первой вложенной div
, второй ко второй и т.д. Разметка проста, легко читается и четко семантична.
Ваш код может быть очень простым, без необходимости беспокоиться о фабриках. Действительно, самый лучший способ сделать это - с пузырьками событий и делегированием, отлично обработанными методом jQuery on
(в 1.7, перед этим используйте delegate
).
$('#list').on('click', 'a', function() {
$('#list').fadeOut(450);
$('#questions div').eq($(this).index()).delay(600).fadeIn(450);
});
Работа jsFiddle (я знаю, что анимация выглядит немного неуклюжей, извините.)
$(this).index()
находит отношение текущего элемента относительно его братьев и сестер. .eq()
фильтрует выбор (элементов #questions div
), чтобы найти элемент в соответствующей позиции.
Код может быть не таким быстрым (если вы используете значения id
, это почти наверняка будет намного быстрее), но ваша разметка проще и, следовательно, более надежна.
Ответ 3
Если вы должны настаивать на том, чтобы не использовать делегирование событий и классы, выполните следующие действия:
$.each( new Array(13), function(index){
$('#p'+(index+1) ).click(function() {
$('#list').fadeOut(450);
$('#q'+(index+1)).delay(600).fadeIn(450)
});
});
Использование классов и делегирования:
$( document ).delegate( ".my-class", "click", function(){
var idIndex = this.id.match( /\d+$/, )[0];
$('#list').fadeOut(450);
$('#q'+idIndex).delay(600).fadeIn(450)
});
Ответ 4
Как и другие, вы можете использовать for-loop
для этого, но на самом деле вы привязываетесь к 14
p
, и всякий раз, когда вы добавляете новый p
, вам нужно увеличить тестовое условие for-loop
.
Это то, что я сделал бы:
$("[id^=p]").bind("click", function() {
$('#list').fadeOut(450);
$('#q' + $(this).attr("id").match(/^#p(\d+)$/i)[1]).delay(600).fadeIn(450)
})
Ответ 5
Не могли бы вы просто использовать класс вместо всех этих разных идентификаторов? А затем какую-нибудь навигацию, чтобы найти соответствующий другой элемент?
$('.myCoolEffect').click(function(event) {
$('#list').fadeOut(450);
// find the matching element based on this on somehow
$(event.currentTarget).up(1).delay(600).fadeIn(450);
});
Ответ 6
Попробуйте это,
$('[id^="p"]').live('click', function(e){
var temp = this.id.match(/^p(\d+)$/);
if(temp){
$('#list').fadeOut(450);
$('#q' + temp[1]).delay(600).fadeIn(450);
}
});
Я использовал jqueryStartsWith Selector и функция live.
Update:
Как, Xion прокомментировал: если вы используете этот код, это повлияет на все остальные элементы, из которых id's
начинаются с p
.
Если бы вы могли добавить больше символов в идентификатор, это не будет проблемой.
Обновление 2:
Как, BrunoLM, упомянутых в комментарии, я немного изменил код, чтобы не инициировать событие для других элементов в dom, которые имеют идентификатор начинается с p
.
var temp = this.id.match(/^p(\d+)$/);
и следующий, если блок позаботится о других элементах, чей id
начинается с p
.
match(/^p(\d+)$/)
возвращает null
для id, как pblabla, p2sdad, pdasds2323
, и возвращает массив для id, например p1, p2, ... p100000....
Ответ 7
$('div').filter(function () {
return this.id.match(/^p([0-9]+)$/);
}).click(function () {
$('#list').fadeOut(450);
$('#' +$(this).attr('id').replace('p','q')).delay(600).fadeIn(450)
});