2010-06-30 7 views
8

Tôi đang sử dụng jQuery và tôi có một điều lạ mà tôi không hiểu. Tôi có một số mã:Những điều kỳ lạ trong JavaScript "cho"

for (i = 1; i <= some_number; i++) { 
    $("#some_button" + i).click(function() { 
     alert(i); 
    }); 
} 

"#some_button" như tên gọi - chúng là một số nút. Khi nhấp vào chúng sẽ bật lên một hộp với số của nó, đúng không? Nhưng họ thì không. Nếu có 4 nút, chúng luôn bật lên "5" (số nút + 1). Tại sao vậy?

+0

Đây là vấn đề rất phổ biến vì hầu hết các câu trả lời đều đề xuất. Ngoài ra, tất cả các câu hỏi được gắn thẻ 'javascript',' closures, 'và' loops' sẽ trỏ đến vấn đề và giải pháp chính xác này - [javascript + closures + loops] (http://stackoverflow.com/questions/tagged/javascript+closures+ vòng lặp), bao gồm cả cái này ngay bây giờ :) – Anurag

+0

chúng ta cần một cách để SO có một cửa sổ pop-up lớn nói rằng "NÀY ĐÃ ĐƯỢC TRẢ LỜI TRƯỚC", với các liên kết đến câu hỏi đúng. Tôi đã nhìn thấy ít nhất 3 phiên bản javascript và 2 phiên bản python hỏi câu hỏi này. – Claudiu

+1

@Claudiu: Thật không may, rất khó cho những người mới đến JavaScript/Python tìm kiếm các loại vấn đề này. –

Trả lời

18

Nó phải làm với phạm vi JavaScript. Bạn có thể nhận được xung quanh nó một cách dễ dàng bằng cách giới thiệu phạm vi khác bằng cách thêm một chức năng và có chức năng gọi bản thân và vượt qua trong tôi:

for (var i = 1; i <= some_number; i++) { 
    (function(j) { 
    $("#some_button" + j).click(function() { 
     alert(j); 
    }); 
    })(i); 
} 

này tạo ra một kết thúc - khi các chức năng bên trong có quyền truy cập vào một phạm vi không còn tồn tại khi hàm được gọi. Xem this article trên MDC để biết thêm thông tin.

CHỈNH SỬA: RE: Các chức năng tự gọi: Chức năng tự gọi là chức năng tự ẩn danh. Bạn không khởi tạo nó cũng như bạn gán nó cho một biến. Nó có dạng như sau (chú ý dấu ngoặc mở):

(function(args) { 
    // function body that might modify args 
})(args_to_pass_in); 

quan này cho câu hỏi, các cơ quan chức năng ẩn danh sẽ là:

$("#some_button" + j).click(function() { 
    alert(j); 
}); 

Kết hợp này lại với nhau, chúng tôi nhận được câu trả lời trong khối mã đầu tiên. Chức năng tự gọi ẩn danh đang chờ một đối số có tên là j. Tìm kiếm bất kỳ phần tử nào có id là some_button có giá trị nguyên là j ở cuối (ví dụ: some_button1, some_button10). Bất kỳ khi nào một trong các thành phần này được nhấp vào, nó sẽ cảnh báo giá trị của j. Dòng thứ hai đến cuối cùng của giải pháp đi qua giá trị i, là bộ đếm vòng lặp mà chức năng tự gọi ẩn danh được gọi. Thực hiện theo cách khác, nó có thể trông giống như sau:

var innerFunction = function(j) { 
    $("#some_button" + j).click(function() { 
    alert(j); 
    }); 
}; 

for (var i = 1; i <= some_number; i++) { 
    innerFunction(i); 
} 
+0

dear god :( Bạn có thể vui lòng, xin vui lòng, viết lại nó một cách dễ đọc hơn? Không ai không 't đã biết câu trả lời sẽ bao giờ hiểu một điều từ mảnh bạn đã viết. ');});}) (i);}' ... –

+1

@SF: đây là khá nhiều những gì bạn phải làm .. Có lẽ điều này sẽ giúp: bạn bật 'for (...) {LOGIC (i); } ', trong đó' LOGIC' là mã tùy ý hoạt động trên biến 'i', thành' for (...) {(FUNC) (i)} ', ngay lập tức tạo một hàm và cho nó' i'. 'FUNC' chỉ đơn giản là' hàm (j) {LOGIC (j); } '. – Claudiu

+0

Có, bạn có thể nghĩ về việc đóng như một hàm có tham chiếu đến mọi biến bên ngoài mà nó sử dụng, chứ không phải là một bản sao. Lỗi này phát sinh bởi vì tôi đã được sử dụng bởi tham chiếu bên trong đóng cửa. Sửa lỗi này giải quyết nó bằng cách sao chép giá trị thành j - bằng cách chuyển nó vào một hàm làm đối số. –

9

Bạn đang có a very common closure problem trong vòng for.

Biến được đính kèm trong một đóng chia sẻ cùng một môi trường duy nhất, do đó, vào thời điểm gọi lại click, vòng lặp sẽ chạy khóa học và biến số i sẽ được trỏ sang mục nhập cuối cùng.

Bạn có thể giải quyết việc này với thậm chí nhiều hơn đóng cửa, sử dụng một nhà máy chức năng:

function makeOnClickCallback(i) { 
    return function() { 
     alert(i); 
    }; 
} 

var i; 

for (i = 0; i < some_number; i++) { 
    $("#some_button" + i).click(makeOnClickCallback(i)); 
} 

này có thể khá một chủ đề phức tạp, nếu bạn không quen thuộc với cách đóng cửa làm việc. Bạn có thể kiểm tra bài viết Mozilla sau cho một giới thiệu ngắn gọn:

-1
(function (some_number) { 
    for (i = 1; i <= some_number; i++) { 
     $("#some_button" + i).click(function() { 
     alert(i); 
     }); 
    } 
    })(some_number); 

Bó chức năng bên ngoài vì cho tốc độ và thực tế tôi sẽ tiếp tục thiết lập lại.

+3

Bạn có chắc chắn rằng bạn đang gói phần bên phải không? – HoLyVieR

2

Vì tại thời điểm bạn nhấp vào chúng, i == 5.

+0

heh rất ngắn gọn, nhưng điều này sẽ chỉ giúp ai đó đã biết vấn đề là gì? – Claudiu

+0

Whoa, chỉ có một upvote 6 tháng sau .. dù sao những gì OP đã thực sự cần là nghiên cứu khái niệm "đóng cửa", sau đó không còn nữa misteries. –

0

Điều này là do cách đóng bao hoạt động trong JavaScript.Mỗi một trong 5 hàm bạn đang tạo là cơ bản chia sẻ cùng một biến số i. Giá trị của i bên trong hàm của bạn không được đánh giá khi bạn tạo hàm, nhưng khi sự kiện nhấp xảy ra, theo đó giá trị của i là 5.

Có nhiều kỹ thuật khác nhau để giải quyết vấn đề này (khi điều này xảy ra) hành vi không phải là những gì bạn muốn). Một (nếu bạn có một hàm đơn giản, giống như bạn làm ở đây) là sử dụng hàm tạo Hàm thay vì hàm theo nghĩa đen:

$("#some_button" + i).click(new Function("alert("+i+")"); 
-1

Đây là mã rất thông minh. Vì vậy, thông minh nó là một câu hỏi về SO. :) Tôi sẽ bỏ qua câu hỏi hoàn toàn bằng cách rút mã xuống, chỉ để có cơ hội hiểu nó (hoặc có một đồng nghiệp hiểu nó) sáu tháng kể từ bây giờ. Đóng cửa có vị trí của họ, nhưng trong trường hợp này tôi muốn tránh chúng ủng hộ mã dễ hiểu hơn.

Có lẽ, tôi sẽ đính kèm cùng chức năng cho tất cả các nút, nút này sẽ nhận được nút từ sự kiện, xóa "some_button" khỏi ID và cảnh báo kết quả. Không gần như xinh đẹp, nhưng tôi đảm bảo tất cả mọi người trong văn phòng có thể theo dõi nó trong nháy mắt.

+0

Có tốt hơn là phải có mã phức tạp để giải quyết vấn đề, hoặc tránh hoàn toàn vấn đề? –