Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
99 views
in Technique[技术] by (71.8m points)

javascript - jQuery code is too verbose, would like pointers on how to make it shorter


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

You can use a for loop, but you should make sure the loop counter's value gets into a correct scope for click event handler:

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

Otherwise the delay and fadeIn would get applied to #q13 element exclusively, since actual counter (with its final value of 13) would get into closure.


EDIT: Since quite a lot of answers got it wrong here, I'll attempt to explain more precisely what's going on in this code, as it seems to be pretty confusing.

The "natural" solution with injecting the click handler directly into loop would be the following:

for(var i = 1; i < 14; i++) {
    $('#p'+i).click(function() {
        $('#list').fadeOut(450);
        $('#q'+i).delay(600).fadeIn(450)
    });
}

But this is not at all equivalent to the extended form, which lists all the 13 variants one after another. The problem is that while there are indeed 13 functions created here, they are all closed over the same variable i, whose value changes. It finally arrives at the value of 13 and the loop ends.

Some time later the functions attached to #p1...#p13 elements are called (when one of those elements are clicked) and they use that final value of i. This results in only #q13 being animated.

What needs to be done here is to do something called lambda lifting and eliminate the free variable i, whose value gets inadvertly changed. A common technique for that is to provide a "factory function" which accepts value for our variable and outputs an actual function which we'll use as event handler:

var clickHandler = function(k) {
    return function() {
        $('#list').fadeOut(450);
        $('#q' + k).delay(600).fadeIn(450);
    };
};

Since the scope of k parameter is local to clickHandler, every call to clickHandler gets different k variable. The function returned from clickHandler is therefore closed over different variables, which can in turn have different values. This is exactly what we need. We can then call clickHandler from our loop, passing it the counter's value:

for (var i = 1; i < 14; ++i) {
    $('#p' + i).click(clickHandler(i));
}

I hope this makes the difference somewhat clearer.


EDIT: As Esailija pointed out in the comments, it is also possible to use jQuery.each to achieve similar effect:

$.each(new Array(13), function(idx) {
    $('#p' + (idx + 1)).click(function() {
        $('#list').fadeOut(450);
        $('#q' + idx).delay(600).fadeIn(450);
    });
});

This is probably the solution of choice if you're already aware of the closure/scoping issue I've tried to outline above.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...