2010-06-01 27 views
10

Tôi đang đọc "Javascript: The Good Parts" và hoàn toàn bị bối rối bởi những gì thực sự xảy ra ở đây. Một lời giải thích chi tiết hơn và/hoặc đơn giản hơn sẽ được đánh giá cao.Đóng cửa: Giải thích từng dòng về ví dụ "Javascript: Good Parts"?

// BAD EXAMPLE 

// Make a function that assigns event handler functions to an array of nodes the wrong way. 
// When you click on a node, an alert box is supposed to display the ordinal of the node. 
// But it always displays the number of nodes instead. 

var add_the_handlers = function (nodes) { 
    var i; 
    for (i = 0; i < nodes.length; i += 1) { 
     nodes[i].onclick = function (e) { 
      alert(i); 
     } 
    } 
}; 

// END BAD EXAMPLE 

Chức năng add_the_handlers nhằm cung cấp cho mỗi trình xử lý một số duy nhất (i). Nó không thành công bởi vì các chức năng xử lý chắc chắn sẽ biến i, không phải là giá trị của biến i lúc các chức năng đã được thực hiện:

// BETTER EXAMPLE 

// Make a function that assigns event handler functions to an array of nodes the right way. 
// When you click on a node, an alert box will display the ordinal of the node. 

var add_the_handlers = function (nodes) { 
    var i; 
    for (i = 0; i < nodes.length; i += 1) { 
     nodes[i].onclick = function (i) { 
      return function (e) { 
       alert(i); 
      }; 
     }(i); 
    } 
}; 

Bây giờ, thay vì gán một chức năng để onclick, chúng ta định nghĩa một hàm và ngay lập tức gọi nó, đi qua số i. Hàm đó sẽ trả về một hàm xử lý sự kiện được gắn với giá trị i đã được chuyển vào, không phải là i được định nghĩa trong add_the_handlers. Hàm trả về đó được gán cho onclick.

+0

Xem câu hỏi được gắn thẻ trong: http://stackoverflow.com/questions/tagged/javascript+closures+loops – CMS

+0

Bạn cũng có thể chơi xung quanh với bản trình diễn trực tiếp http://jsbin.com/sezisalulede/1/edit?html,js,output –

Trả lời

20

Tôi nghĩ đây là nguồn gây nhầm lẫn rất phổ biến đối với người mới sử dụng JavaScript. Trước tiên tôi xin đề nghị kiểm tra ra các bài viết Mozilla Dev sau đây để giới thiệu ngắn gọn về chủ đề đóng cửa và Phạm vi từ vựng:

Hãy bắt đầu với cái xấu một:

var add_the_handlers = function (nodes) { 
// Variable i is declared in the local scope of the add_the_handlers() 
// function. 
    var i; 

// Nothing special here. A normal for loop. 
    for (i = 0; i < nodes.length; i += 1) { 

// Now we are going to assign an anonymous function to the onclick property. 
     nodes[i].onclick = function (e) { 

// The problem here is that this anonymous function has become a closure. It 
// will be sharing the same local variable environment as the add_the_handlers() 
// function. Therefore when the callback is called, the i variable will contain 
// the last value it had when add_the_handlers() last returned. 
      alert(i); 
     } 
    } 

// The for loop ends, and i === nodes.length. The add_the_handlers() maintains 
// the value of i even after it returns. This is why when the callback 
// function is invoked, it will always alert the value of nodes.length. 
}; 

Chúng tôi có thể giải quyết vấn đề này với nhiều đóng cửa hơn, như Crockford đã đề xuất trong "ví dụ tốt". Một bao đóng là một loại đối tượng đặc biệt kết hợp hai thứ: một hàm và môi trường trong đó hàm đó được tạo ra. Trong JavaScript, môi trường của việc đóng cửa bao gồm bất kỳ biến địa phương mà là trong phạm vi tại thời điểm đó đóng cửa đã được tạo ra:

// Now we are creating an anonymous closure that creates its own local 
// environment. I renamed the parameter variable x to make it more clear. 
nodes[i].onclick = function (x) { 

    // Variable x will be initialized when this function is called. 

    // Return the event callback function. 
    return function (e) { 
     // We use the local variable from the closure environment, and not the 
     // one held in the scope of the outer function add_the_handlers(). 
     alert(x); 
    }; 
}(i); // We invoke the function immediately to initialize its internal 
     // environment that will be captured in the closure, and to receive 
     // the callback function which we need to assign to the onclick. 

Thay vì có callbacks tất cả chia sẻ một môi trường duy nhất, chức năng đóng cửa tạo ra một môi trường mới cho mỗi người. Chúng tôi cũng có thể sử dụng một nhà máy chức năng để tạo ra một đóng cửa, như trong ví dụ sau:

function makeOnClickCallback (x) { 
    return function (e) { 
     alert(x); 
    }; 
} 

for (i = 0; i < nodes.length; i += 1) { 
    nodes[i].onclick = makeOnClickCallback(i); 
} 
+0

Một câu hỏi có liên quan. "E" có nghĩa là gì trong hàm (e), và nó có thể được thay thế bằng bất kỳ var nào không? Tôi đã từng nghĩ nó có nghĩa là sự kiện, nhưng bây giờ tôi đang bối rối. – Matrym

+0

@Matrym: Có, phải là một đối số mà trình duyệt chuyển đến hàm gọi lại khi sự kiện onclick được nâng lên. Kiểm tra [bài viết quirksmode này] (http://www.quirksmode.org/js/events_access.html) về cách điều này được xử lý trong các trình duyệt khác nhau. –

+0

Điều gì xảy ra nếu chúng tôi không sử dụng biến? Chúng ta có vượt qua để chúng ta có thể chuỗi các công cụ không? – Matrym

3

Đó là tất cả về đóng cửa. Trong ví dụ đầu tiên, "i" sẽ bằng "nodes.length" cho mọi trình xử lý sự kiện nhấp chuột, bởi vì nó sử dụng "i" từ vòng lặp tạo bộ xử lý sự kiện. Vào lúc xử lý sự kiện được gọi, vòng lặp sẽ kết thúc, vì vậy "i" sẽ bằng "nodes.length".

Trong ví dụ thứ hai, "i" là một tham số (do đó, một biến cục bộ). Trình xử lý sự kiện sẽ sử dụng giá trị của biến cục bộ "i" (tham số).

0

Nó có liên quan đến việc đóng cửa.

Khi bạn làm điều trong ví dụ xấu,

khi bạn nhấp vào mỗi nút, bạn sẽ nhận được giá trị i mới nhất (ví dụ: bạn có 3 nút, không có vấn đề gì nút bạn nhấp chuột, bạn sẽ nhận được 2). kể từ khi cảnh báo của bạn (i) được ràng buộc với một tham chiếu của biến i và không phải là giá trị của i tại thời điểm nó đã được ràng buộc trong xử lý sự kiện.

Làm điều đó theo cách tốt hơn, bạn ràng buộc nó vào thời điểm mà nó đã được lặp lại, vì vậy nhấp vào nút 1 sẽ cung cấp cho bạn 0, nút 2 sẽ cung cấp cho bạn 1 và nút 3 sẽ cung cấp cho bạn 2

về cơ bản, bạn đang đánh giá những gì tôi là ngay lập tức khi nó được gọi tại dòng} (i) và nó đã được chuyển đến tham số e mà bây giờ giữ giá trị của những gì tôi là tại thời điểm đó trong thời gian.

Btw ... Tôi nghĩ có lỗi đánh máy trong phần ví dụ tốt hơn ... cần cảnh giác (e) thay vì cảnh báo (i).

2

Trong cả hai ví dụ, bất kỳ nút nào được truyền đều có trình xử lý sự kiện onclick được liên kết với nó (giống như <img src="..." onclick="myhandler()"/>, thực tế là không tốt).

Sự khác biệt là trong ví dụ xấu mỗi đóng (các hàm xử lý sự kiện, nghĩa là) đang tham chiếu chính xác cùng một biến số i do phạm vi chung của chúng.

Ví dụ tốt sử dụng chức năng ẩn danh được thực thi ngay lập tức. Hàm ẩn danh này tham chiếu chính xác biến số i như trong ví dụ xấu NHƯNG vì nó được thực hiện và được cung cấp với tham số đầu tiên là i, giá trị của i được gán cho một biến cục bộ được gọi là ... eh? ... i, chính xác - do đó ghi đè lên quy tắc được xác định trong phạm vi của phụ huynh.

Hãy viết lại ví dụ điển hình để làm cho nó tất cả rõ ràng:

var add_the_handlers = function (nodes) { 
    var i; 
    for (i = 0; i < nodes.length; i += 1) { 
     nodes[i].onclick = function (newvar) { 
      return function (e) { 
       alert(nevar); 
      }; 
     }(i); 
    } 
}; 

Ở đây chúng ta thay thế i trong hàm xử lý sự kiện trở lại với newvar và nó vẫn hoạt động, vì newvar là chỉ là những gì bạn mong muốn - một biến mới được kế thừa từ phạm vi chức năng ẩn danh.

Chúc bạn may mắn.

Các vấn đề liên quan