Câu hỏi JavaScript đóng hoạt động như thế nào?


Làm thế nào bạn sẽ giải thích JavaScript đóng cửa cho một ai đó với một kiến ​​thức về các khái niệm mà họ bao gồm (ví dụ chức năng, biến và tương tự), nhưng không hiểu đóng cửa bản thân?

tôi đa tưng thây ví dụ Scheme được đưa ra trên Wikipedia, nhưng tiếc là nó không giúp được gì.


7654


gốc


Vấn đề của tôi với những câu trả lời này là chúng tiếp cận nó từ một quan điểm trừu tượng, lý thuyết, thay vì bắt đầu giải thích đơn giản là tại sao các bao đóng lại cần thiết trong Javascript và các tình huống thực tế mà bạn sử dụng chúng. Bạn kết thúc với một bài viết tl; dr mà bạn phải để thông qua, tất cả thời gian suy nghĩ, "nhưng, tại sao?". Tôi chỉ đơn giản là bắt đầu với: đóng cửa là một cách gọn gàng để đối phó với hai thực tế sau đây của JavaScript: a. phạm vi ở cấp chức năng, không phải ở cấp khối và, b. phần lớn những gì bạn làm trong thực tế trong JavaScript là điều khiển không đồng bộ / sự kiện. - Jeremy Burton
@Redsandro Đối với một, nó làm cho mã sự kiện điều khiển dễ dàng hơn nhiều để viết. Tôi có thể kích hoạt một chức năng khi trang tải để xác định chi tiết cụ thể về HTML hoặc các tính năng có sẵn. Tôi có thể định nghĩa và thiết lập một trình xử lý trong hàm đó và có tất cả các thông tin ngữ cảnh đó có sẵn mỗi khi trình xử lý được gọi mà không phải truy vấn lại nó. Giải quyết vấn đề một lần, tái sử dụng trên mỗi trang mà xử lý đó là cần thiết với giảm chi phí trên xử lý lại yêu cầu. Bạn đã bao giờ thấy cùng một dữ liệu được tái ánh xạ hai lần trong một ngôn ngữ không có chúng? Việc đóng cửa giúp dễ dàng tránh những thứ đó. - Erik Reppen
Đối với các lập trình viên Java, câu trả lời ngắn gọn là hàm tương đương với một lớp bên trong. Một lớp bên trong cũng chứa một con trỏ ngầm tới một thể hiện của lớp bên ngoài, và được sử dụng cho nhiều mục đích tương tự (có nghĩa là, tạo các trình xử lý sự kiện). - Boris van Schooten
Hiểu điều này tốt hơn nhiều từ đây: javascriptissexy.com/understand-javascript-closures-with-ease. Vẫn cần một đóng cửa về đóng cửa sau khi đọc các câu trả lời khác. :) - Akhoy
Tôi thấy ví dụ thực tế này rất hữu ích: youtube.com/watch?v=w1s9PgtEoJs - Abhi


Các câu trả lời:


Đóng JavaScript cho người mới bắt đầu

Do Morris on Tue đệ trình, 2006-02-21 10:19. Cộng đồng được chỉnh sửa kể từ đó.

Đóng cửa không phải là ma thuật

Trang này giải thích các bao đóng để một lập trình viên có thể hiểu chúng - bằng cách sử dụng mã JavaScript đang hoạt động. Nó không dành cho rất kinh nghiệm hay lập trình viên chức năng.

Đóng cửa là không khó để hiểu một khi khái niệm cốt lõi là grokked. Tuy nhiên, họ không thể hiểu được bằng cách đọc bất kỳ tài liệu học thuật hoặc thông tin định hướng học tập về họ!

Bài viết này dành cho các lập trình viên có một số kinh nghiệm lập trình bằng ngôn ngữ chính thống và ai có thể đọc hàm JavaScript sau:

function sayHello(name) {
  var text = 'Hello ' + name;
  var say = function() { console.log(text); }
  say();
}
sayHello('Joe');

Một ví dụ về việc đóng cửa

Hai tóm tắt một câu:

  • Việc đóng cửa là một cách hỗ trợ chức năng hạng nhất; nó là một biểu thức có thể tham chiếu các biến trong phạm vi của nó (khi nó được khai báo lần đầu tiên), được gán cho một biến, được chuyển như một đối số cho hàm, hoặc được trả về dưới dạng kết quả hàm.

  • Hoặc, một đóng là một khung ngăn xếp được phân bổ khi một hàm bắt đầu thực hiện, và không được giải phóng sau khi hàm trả về (như thể một 'stack frame' được cấp phát trên heap chứ không phải là stack!).

Đoạn mã sau trả về một tham chiếu đến một hàm:

function sayHello2(name) {
  var text = 'Hello ' + name; // Local variable
  var say = function() { console.log(text); }
  return say;
}
var say2 = sayHello2('Bob');
say2(); // logs "Hello Bob"

Hầu hết các lập trình viên JavaScript sẽ hiểu cách tham chiếu đến một hàm được trả về một biến (say2) trong đoạn mã trên. Nếu không, thì bạn cần phải xem xét điều đó trước khi bạn có thể học được bao đóng. Một lập trình viên sử dụng C sẽ nghĩ về hàm như trả về một con trỏ tới một hàm và các biến say và say2 từng là một con trỏ đến một hàm.

Có sự khác biệt quan trọng giữa con trỏ C với hàm và tham chiếu JavaScript đến hàm. Trong JavaScript, bạn có thể nghĩ về biến tham chiếu hàm như có cả con trỏ tới hàm cũng như một con trỏ ẩn để đóng.

Đoạn mã trên có một đóng vì hàm ẩn danh function() { console.log(text); } được khai báo phía trong một hàm khác, sayHello2() trong ví dụ này. Trong JavaScript, nếu bạn sử dụng function từ khóa bên trong một hàm khác, bạn đang tạo một đóng.

Trong C và hầu hết các ngôn ngữ phổ biến khác, sau một hàm trả về, tất cả các biến cục bộ không còn có thể truy cập được vì khung ngăn xếp bị hủy.

Trong JavaScript, nếu bạn khai báo một hàm trong một hàm khác, thì các biến cục bộ có thể vẫn có thể truy cập được sau khi trở về từ hàm mà bạn đã gọi. Điều này được thể hiện ở trên, bởi vì chúng ta gọi hàm say2()sau khi chúng tôi đã trở về từ sayHello2(). Lưu ý rằng mã mà chúng tôi gọi là tham chiếu biến text, đó là một biến cục bộ của hàm sayHello2().

function() { console.log(text); } // Output of say2.toString();

Nhìn vào đầu ra của say2.toString(), chúng ta có thể thấy rằng mã đề cập đến biến text. Hàm ẩn danh có thể tham chiếu text giữ giá trị 'Hello Bob' vì các biến cục bộ của sayHello2() được giữ trong một đóng cửa.

Điều kỳ diệu là trong JavaScript một tham chiếu hàm cũng có một tham chiếu bí mật đến việc đóng nó được tạo ra - tương tự như cách các delegate là một con trỏ phương thức cộng với một tham chiếu bí mật tới một đối tượng.

Ví dụ khác

Vì một số lý do, việc đóng cửa có vẻ thực sự khó hiểu khi bạn đọc về chúng, nhưng khi bạn thấy một số ví dụ, nó trở nên rõ ràng cách chúng hoạt động (nó mất một thời gian). Tôi khuyên bạn nên làm việc qua các ví dụ một cách cẩn thận cho đến khi bạn hiểu cách chúng hoạt động. Nếu bạn bắt đầu sử dụng các bao đóng mà không hiểu đầy đủ cách chúng hoạt động, bạn sẽ sớm tạo ra một số lỗi rất lạ!

Ví dụ 3

Ví dụ này cho thấy rằng các biến cục bộ không được sao chép - chúng được lưu giữ theo tham chiếu. Nó giống như giữ một ngăn xếp trong bộ nhớ khi chức năng bên ngoài thoát ra!

function say667() {
  // Local variable that ends up within closure
  var num = 42;
  var say = function() { console.log(num); }
  num++;
  return say;
}
var sayNumber = say667();
sayNumber(); // logs 43

Ví dụ 4

Tất cả ba hàm toàn cầu có một tham chiếu chung cho tương tự đóng cửa bởi vì tất cả đều được khai báo trong một cuộc gọi duy nhất setupSomeGlobals().

var gLogNumber, gIncreaseNumber, gSetNumber;
function setupSomeGlobals() {
  // Local variable that ends up within closure
  var num = 42;
  // Store some references to functions as global variables
  gLogNumber = function() { console.log(num); }
  gIncreaseNumber = function() { num++; }
  gSetNumber = function(x) { num = x; }
}

setupSomeGlobals();
gIncreaseNumber();
gLogNumber(); // 43
gSetNumber(5);
gLogNumber(); // 5

var oldLog = gLogNumber;

setupSomeGlobals();
gLogNumber(); // 42

oldLog() // 5

Ba hàm đã chia sẻ quyền truy cập vào cùng một kết nối - các biến cục bộ của setupSomeGlobals() khi ba hàm được xác định.

Lưu ý rằng trong ví dụ trên, nếu bạn gọi setupSomeGlobals() một lần nữa, sau đó một đóng mới (stack-frame!) được tạo ra. Người già gLogNumber, gIncreaseNumber, gSetNumber các biến được ghi đè bằng Mới các hàm có phần đóng mới. (Trong JavaScript, bất cứ khi nào bạn khai báo một hàm bên trong một hàm khác, các hàm bên trong được / được tái tạo lại mỗi thời gian hàm bên ngoài được gọi.)

Ví dụ 5

Ví dụ này cho thấy rằng việc đóng chứa bất kỳ biến cục bộ nào được khai báo bên trong hàm bên ngoài trước khi nó thoát. Lưu ý rằng biến alice thực sự được khai báo sau hàm ẩn danh. Hàm ẩn danh được khai báo trước tiên; và khi chức năng đó được gọi là nó có thể truy cập alice biến vì alice nằm trong cùng một phạm vi (JavaScript biến cẩu). Cũng thế sayAlice()() chỉ cần gọi trực tiếp hàm tham chiếu hàm được trả về từ sayAlice() - nó chính xác giống như những gì đã được thực hiện trước đây nhưng không có biến tạm thời.

function sayAlice() {
    var say = function() { console.log(alice); }
    // Local variable that ends up within closure
    var alice = 'Hello Alice';
    return say;
}
sayAlice()();// logs "Hello Alice"

Tricky: cũng lưu ý rằng say biến cũng nằm trong phần đóng và có thể được truy cập bởi bất kỳ hàm nào khác có thể được khai báo bên trong sayAlice()hoặc nó có thể được truy cập đệ quy trong hàm bên trong.

Ví dụ 6

Đây là một bản ghi thực sự cho nhiều người, vì vậy bạn cần phải hiểu nó. Hãy rất cẩn thận nếu bạn định nghĩa một hàm trong vòng lặp: các biến cục bộ từ việc đóng có thể không hoạt động như bạn nghĩ trước.

Bạn cần hiểu tính năng "hoán đổi biến" trong Javascript để hiểu ví dụ này.

function buildList(list) {
    var result = [];
    for (var i = 0; i < list.length; i++) {
        var item = 'item' + i;
        result.push( function() {console.log(item + ' ' + list[i])} );
    }
    return result;
}

function testList() {
    var fnlist = buildList([1,2,3]);
    // Using j only to help prevent confusion -- could use i.
    for (var j = 0; j < fnlist.length; j++) {
        fnlist[j]();
    }
}

 testList() //logs "item2 undefined" 3 times

Dòng result.push( function() {console.log(item + ' ' + list[i])} thêm một tham chiếu đến một hàm nặc danh ba lần vào mảng kết quả. Nếu bạn không quen thuộc với các chức năng nặc danh, hãy nghĩ về nó như:

pointer = function() {console.log(item + ' ' + list[i])};
result.push(pointer);

Lưu ý rằng khi bạn chạy ví dụ, "item2 undefined" được ghi lại ba lần! Điều này là do giống như các ví dụ trước, chỉ có một đóng cho các biến cục bộ cho buildList (đó là result, i và item). Khi các hàm ẩn danh được gọi trên dòng fnlist[j](); tất cả đều sử dụng cùng một kết nối duy nhất và họ sử dụng giá trị hiện tại cho i và item trong đó một đóng cửa (ở đâu i có giá trị 3 bởi vì vòng lặp đã hoàn thành và item có giá trị 'item2'). Lưu ý chúng tôi đang lập chỉ mục từ 0 do đó item có giá trị item2. Và i + + sẽ tăng i với giá trị 3.

Có thể hữu ích khi xem điều gì sẽ xảy ra khi khai báo cấp khối của biến item được sử dụng (thông qua let từ khóa) thay vì khai báo biến có phạm vi chức năng thông qua var từ khóa. Nếu thay đổi đó được thực hiện, thì mỗi hàm ẩn danh trong mảng result có đóng cửa riêng; khi ví dụ chạy đầu ra như sau:

item0 undefined
item1 undefined
item2 undefined

Nếu biến i cũng được xác định bằng cách sử dụng let thay vì var, sau đó đầu ra là:

item0 1
item1 2
item2 3

Ví dụ 7

Trong ví dụ cuối cùng này, mỗi cuộc gọi đến hàm chính sẽ tạo ra một kết thúc riêng biệt.

function newClosure(someNum, someRef) {
    // Local variables that end up within closure
    var num = someNum;
    var anArray = [1,2,3];
    var ref = someRef;
    return function(x) {
        num += x;
        anArray.push(num);
        console.log('num: ' + num +
            '; anArray: ' + anArray.toString() +
            '; ref.someVar: ' + ref.someVar + ';');
      }
}
obj = {someVar: 4};
fn1 = newClosure(4, obj);
fn2 = newClosure(5, obj);
fn1(1); // num: 5; anArray: 1,2,3,5; ref.someVar: 4;
fn2(1); // num: 6; anArray: 1,2,3,6; ref.someVar: 4;
obj.someVar++;
fn1(2); // num: 7; anArray: 1,2,3,5,7; ref.someVar: 5;
fn2(2); // num: 8; anArray: 1,2,3,6,8; ref.someVar: 5;

Tóm lược

Nếu mọi thứ có vẻ hoàn toàn không rõ ràng thì điều tốt nhất cần làm là chơi với các ví dụ. Đọc một lời giải thích là khó hơn nhiều so với các ví dụ hiểu biết. Giải thích của tôi về bao đóng và khung ngăn xếp, v.v. không chính xác về mặt kỹ thuật - chúng là những đơn giản hóa tổng thể nhằm giúp hiểu. Một khi ý tưởng cơ bản là grokked, bạn có thể nhận các chi tiết sau này.

Điểm cuối cùng:

  • Bất cứ khi nào bạn sử dụng function bên trong một hàm khác, một bao đóng được sử dụng.
  • Bất cứ khi nào bạn sử dụng eval() bên trong một hàm, một bao đóng được sử dụng. Văn bản bạn eval có thể tham chiếu các biến cục bộ của hàm và bên trong eval bạn thậm chí có thể tạo các biến cục bộ mới bằng cách sử dụng eval('var foo = …')
  • Khi bạn sử dụng new Function(…) (các Hàm tạo hàm) bên trong một hàm, nó không tạo ra một đóng. (Hàm mới không thể tham chiếu các biến cục bộ của hàm bên ngoài.)
  • Việc đóng trong JavaScript giống như giữ một bản sao của tất cả các biến cục bộ, giống như khi một hàm bị thoát.
  • Nó có lẽ là tốt nhất để nghĩ rằng một đóng cửa luôn luôn được tạo ra chỉ là một mục nhập vào một chức năng, và các biến địa phương được thêm vào đó đóng cửa.
  • Một tập hợp các biến cục bộ mới được lưu giữ mỗi lần một hàm với một đóng được gọi (được cho là hàm chứa một khai báo hàm bên trong nó, và một tham chiếu đến hàm bên trong đó được trả về hoặc một tham chiếu bên ngoài được giữ cho nó theo một cách nào đó ).
  • Hai chức năng có thể trông giống như chúng có cùng văn bản nguồn, nhưng có hành vi hoàn toàn khác nhau do đóng cửa 'ẩn' của chúng. Tôi không nghĩ rằng mã JavaScript thực sự có thể tìm ra nếu một tham chiếu chức năng có đóng cửa hay không.
  • Nếu bạn đang cố gắng thực hiện bất kỳ sửa đổi mã nguồn động nào (ví dụ: myFunction = Function(myFunction.toString().replace(/Hello/,'Hola'));), nó sẽ không hoạt động nếu myFunction là một đóng cửa (tất nhiên, bạn sẽ không bao giờ nghĩ đến việc thay thế chuỗi mã nguồn trong thời gian chạy, nhưng ...).
  • Có thể nhận được các khai báo hàm trong các khai báo hàm trong các hàm - và bạn có thể nhận được các bao đóng ở nhiều cấp.
  • Tôi nghĩ rằng việc đóng cửa là một thuật ngữ cho cả hàm cùng với các biến được bắt giữ. Lưu ý rằng tôi không sử dụng định nghĩa đó trong bài viết này!
  • Tôi nghi ngờ rằng các bao đóng trong JavaScript khác với những cái thường được tìm thấy trong các ngôn ngữ chức năng.

Liên kết

Cảm ơn

Nếu bạn có chỉ đóng cửa đã học (ở đây hoặc ở nơi khác!), sau đó tôi quan tâm đến bất kỳ phản hồi nào từ bạn về bất kỳ thay đổi nào bạn có thể đề xuất có thể làm cho bài viết này rõ ràng hơn. Gửi email đến morrisjohns.com (morris_closure @). Xin lưu ý rằng tôi không phải là một guru trên JavaScript - cũng không phải trên đóng cửa.


Bài gốc của Morris có thể được tìm thấy trong Lưu trữ Internet.


6173



Rực rỡ. Tôi đặc biệt yêu thích: "Việc đóng cửa bằng JavaScript giống như giữ một bản sao của tất cả các biến cục bộ, giống như khi chúng đã thoát khỏi một hàm." - e-satis
@ e-satis - Rực rỡ như nó có vẻ, "một bản sao của tất cả các biến địa phương, cũng giống như khi chức năng thoát" là gây hiểu lầm. Nó cho thấy rằng các giá trị của các biến được sao chép, nhưng thực sự nó là tập các biến mà chúng không thay đổi sau khi hàm được gọi (ngoại trừ 'eval' có thể: blog.rakeshpai.me/2008/10/…). Nó gợi ý rằng hàm phải trả về trước khi kết thúc được tạo ra, nhưng nó không cần phải trả về trước khi đóng cửa có thể được sử dụng như một đóng. - dlaliberte
Điều này nghe có vẻ tốt đẹp: "Việc đóng cửa trong JavaScript giống như giữ một bản sao của tất cả các biến cục bộ, giống như khi một hàm đã thoát." Nhưng nó là gây hiểu lầm cho một vài lý do. (1) Các cuộc gọi chức năng không phải thoát ra để tạo ra một đóng cửa. (2) Nó không phải là một bản sao của giá trị của các biến địa phương nhưng chính các biến đó. (3) Nó không nói ai có quyền truy cập vào các biến này. - dlaliberte
Ví dụ 5 cho thấy một "gotcha", nơi mã không hoạt động như dự định. Nhưng nó không cho thấy làm thế nào để sửa chữa nó. Câu trả lời khác này cho thấy một cách để làm điều đó. - Matt
Tôi thích cách bài viết này bắt đầu với chữ in đậm lớn nói "Đóng cửa không phải là ma thuật" và kết thúc ví dụ đầu tiên của nó với "Sự kỳ diệu là trong JavaScript một tham chiếu hàm cũng có một tham chiếu bí mật về việc đóng nó được tạo ra trong". - Andrew Macheret


Bất cứ khi nào bạn thấy từ khóa chức năng bên trong một hàm khác, hàm bên trong có quyền truy cập vào các biến trong hàm bên ngoài.

function foo(x) {
  var tmp = 3;

  function bar(y) {
    console.log(x + y + (++tmp)); // will log 16
  }

  bar(10);
}

foo(2);

Điều này sẽ luôn luôn đăng nhập 16, bởi vì bar có thể truy cập x được định nghĩa là một đối số foovà cũng có thể truy cập tmp từ foo.

Cái đó  đóng cửa. Một hàm không cần phải trở về để được gọi là đóng cửa. Chỉ cần truy cập các biến nằm ngoài phạm vi từ vựng ngay lập tức của bạn sẽ tạo ra một sự đóng.

function foo(x) {
  var tmp = 3;

  return function (y) {
    console.log(x + y + (++tmp)); // will also log 16
  }
}

var bar = foo(2); // bar is now a closure.
bar(10);

Hàm trên cũng sẽ đăng nhập 16, bởi vì bar vẫn có thể tham khảo x và tmp, mặc dù nó không còn trực tiếp trong phạm vi.

Tuy nhiên, kể từ tmp vẫn đang treo xung quanh bên trong bar's đóng cửa, nó cũng đang được tăng lên. Nó sẽ được tăng lên mỗi lần bạn gọi bar.

Ví dụ đơn giản nhất về việc đóng cửa là:

var a = 10;
function test() {
  console.log(a); // will output 10
  console.log(b); // will output 6
}
var b = 6;
test();

Khi một hàm JavaScript được gọi, một ngữ cảnh thực thi mới được tạo ra. Cùng với các đối số hàm và đối tượng cha, ngữ cảnh thực thi này cũng nhận tất cả các biến được khai báo bên ngoài nó (trong ví dụ trên, cả 'a' và 'b').

Có thể tạo nhiều hơn một hàm đóng, hoặc bằng cách trả về một danh sách của chúng hoặc bằng cách đặt chúng thành các biến toàn cầu. Tất cả những điều này sẽ ám chỉ đến tương tự  x và giống như vậy tmp, họ không tạo bản sao của riêng họ.

Đây là số x là một chữ số. Như với các chữ khác trong JavaScript, khi foo được gọi là số x Là đã sao chép vào foo làm đối số của nó x.

Mặt khác, JavaScript luôn sử dụng các tham chiếu khi xử lý các đối tượng. Nếu nói, bạn gọi foovới một đối tượng, nó sẽ trả về tài liệu tham khảo đối tượng ban đầu đó!

function foo(x) {
  var tmp = 3;

  return function (y) {
    console.log(x + y + tmp);
    x.memb = x.memb ? x.memb + 1 : 1;
    console.log(x.memb);
  }
}

var age = new Number(2);
var bar = foo(age); // bar is now a closure referencing age.
bar(10);

Như mong đợi, mỗi cuộc gọi đến bar(10) sẽ tăng x.memb. Những gì có thể không được mong đợi, đó là x chỉ đơn giản là đề cập đến cùng một đối tượng như age biến! Sau một vài cuộc gọi đến bar, age.memb sẽ là 2! Tham chiếu này là cơ sở cho rò rỉ bộ nhớ với các đối tượng HTML.


3808



@feeela: Có, mọi chức năng JS đều tạo ra sự đóng cửa. Các biến không được tham chiếu sẽ có khả năng đủ điều kiện để thu thập rác trong các công cụ JS hiện đại, nhưng nó không thay đổi thực tế là khi bạn tạo ngữ cảnh thực thi, ngữ cảnh đó có tham chiếu đến ngữ cảnh thực thi kèm theo và các biến của nó, hàm đó là một đối tượng có khả năng được di chuyển đến một phạm vi biến khác, trong khi vẫn giữ nguyên tham chiếu gốc đó. Đó là việc đóng cửa.
@Ali Tôi vừa phát hiện ra rằng jsFiddle tôi đã cung cấp không thực sự chứng minh bất cứ điều gì, vì delete không thành công. Tuy nhiên, môi trường từ vựng mà hàm sẽ mang theo như [[Scope]] (và cuối cùng sử dụng làm cơ sở cho môi trường từ vựng riêng của nó khi được gọi) được xác định khi câu lệnh định nghĩa hàm được thực hiện. Điều này có nghĩa là hàm Là đóng trên các nội dung ENTIRE của phạm vi thực hiện, bất kể giá trị nó thực sự đề cập đến và liệu nó có thoát khỏi phạm vi hay không. Vui lòng xem các phần 13.2 và 10 trong thông số kỹ thuật - Asad Saeeduddin
Đây là một câu trả lời tốt cho đến khi nó cố gắng giải thích các kiểu nguyên thủy và các tham chiếu. Nó hoàn toàn sai và nói về các chữ được sao chép, điều đó thực sự không liên quan gì đến bất cứ điều gì. - Ry-♦
Các đóng là câu trả lời của JavaScript đối với lập trình hướng đối tượng dựa trên lớp. JS không phải là lớp dựa trên, do đó, người ta phải tìm một cách khác để thực hiện một số điều mà không thể được thực hiện khác. - Barth Zalewski
đây phải là câu trả lời được chấp nhận. Phép thuật không bao giờ xảy ra trong hàm bên trong. Nó xảy ra khi gán hàm bên ngoài cho một biến. Điều này tạo ra một bối cảnh thực hiện mới cho hàm bên trong, do đó, "biến riêng" có thể được tích lũy. Tất nhiên nó có thể kể từ khi biến mà hàm bên ngoài được gán cho đã duy trì ngữ cảnh. Câu trả lời đầu tiên chỉ làm cho toàn bộ điều phức tạp hơn mà không giải thích những gì thực sự xảy ra ở đó. - Albert Gao


FOREWORD: câu trả lời này được viết khi câu hỏi là:

Giống như Albert già nói: "Nếu bạn không thể giải thích nó cho một đứa trẻ sáu tuổi, bạn thực sự không hiểu nó cho mình." Vâng, tôi đã cố gắng giải thích JS đóng cửa cho một người bạn 27 tuổi và hoàn toàn thất bại.

Ai có thể xem xét rằng tôi 6 và kỳ lạ quan tâm đến chủ đề đó?

Tôi khá chắc chắn rằng tôi là một trong những người duy nhất cố gắng lấy câu hỏi ban đầu theo nghĩa đen. Kể từ đó, câu hỏi đã biến đổi nhiều lần, vì vậy câu trả lời của tôi bây giờ có vẻ ngớ ngẩn và không đúng chỗ. Hy vọng rằng ý tưởng chung của câu chuyện vẫn vui vẻ đối với một số người.


Tôi là một fan hâm mộ lớn của sự tương tự và ẩn dụ khi giải thích các khái niệm khó khăn, vì vậy hãy để tôi thử tay của tôi với một câu chuyện.

Ngay xửa ngay xưa:

Có một công chúa ...

function princess() {

Cô sống trong một thế giới tuyệt vời đầy phiêu lưu. Cô gặp Hoàng tử Charming, cưỡi quanh thế giới của mình trên một con kỳ lân, chiến đấu với những con rồng, bắt gặp những con vật nói chuyện, và nhiều thứ kỳ quái khác.

    var adventures = [];

    function princeCharming() { /* ... */ }

    var unicorn = { /* ... */ },
        dragons = [ /* ... */ ],
        squirrel = "Hello!";

    /* ... */

Nhưng cô ấy sẽ luôn phải quay trở lại thế giới nhàm chán của mình về công việc nhà và trưởng thành.

    return {

Và cô ấy thường nói với họ về cuộc phiêu lưu tuyệt vời mới nhất của cô ấy như một công chúa.

        story: function() {
            return adventures[adventures.length - 1];
        }
    };
}

Nhưng tất cả những gì họ thấy là một cô bé ...

var littleGirl = princess();

... kể chuyện về ma thuật và tưởng tượng.

littleGirl.story();

Và mặc dù những người trưởng thành biết về các công chúa thực sự, họ sẽ không bao giờ tin vào những con kỳ lân hay rồng bởi vì họ không bao giờ có thể nhìn thấy chúng. Những người trưởng thành nói rằng họ chỉ tồn tại bên trong trí tưởng tượng của cô bé.

Nhưng chúng ta biết sự thật thực sự; rằng cô bé với công chúa bên trong ...

... thực sự là một công chúa với một cô bé bên trong.


2253



Tôi thực sự thích lời giải thích này. Đối với những người đọc nó và không làm theo, sự tương tự là: công chúa () chức năng là một phạm vi phức tạp có chứa dữ liệu cá nhân. Bên ngoài hàm, không thể xem hoặc truy cập dữ liệu riêng tư. Công chúa giữ những con kỳ lân, rồng, phiêu lưu, vv trong trí tưởng tượng của cô ấy (dữ liệu cá nhân) và những người trưởng thành không thể nhìn thấy chúng cho chính mình. NHƯNG trí tưởng tượng của công chúa bị bắt trong đóng cửa cho story()chức năng, đó là giao diện duy nhất littleGirl ví dụ lộ ra thế giới ma thuật. - Patrick M
Nên ở đây story là đóng cửa nhưng đã có mã var story = function() {}; return story; sau đó littleGirl sẽ đóng cửa. Ít nhất đó là ấn tượng mà tôi nhận được từ MDN sử dụng các phương thức 'riêng tư' với các bao đóng: "Ba chức năng công cộng đó là các bao đóng chia sẻ cùng một môi trường." - icc97
@ icc97, vâng, story là một đóng cửa đề cập đến môi trường được cung cấp trong phạm vi princess. princess cũng là một bao hàm đóng cửa, tức là princess và littleGirl sẽ chia sẻ mọi tham chiếu đến parents mảng có thể tồn tại trong môi trường / phạm vi nơi littleGirl tồn tại và princess được định nghĩa. - Jacob Swartwood
@BenjaminKrupp Tôi đã thêm một nhận xét mã rõ ràng để hiển thị / ngụ ý rằng có nhiều hoạt động hơn trong nội dung của princess hơn những gì được viết. Thật không may câu chuyện này bây giờ là một chút ra khỏi vị trí trên chủ đề này. Ban đầu câu hỏi đã được yêu cầu để "giải thích đóng cửa JavaScript cho một 5yr cũ"; phản ứng của tôi là người duy nhất thậm chí cố gắng làm điều đó. Tôi không nghi ngờ rằng nó sẽ thất bại thảm hại, nhưng ít nhất phản ứng này có thể đã có cơ hội để giữ được sự quan tâm của 5yr tuổi. - Jacob Swartwood
Trên thực tế, với tôi điều này có ý nghĩa hoàn hảo. Và tôi phải thừa nhận, cuối cùng sự hiểu biết một đóng cửa JS bằng cách sử dụng những câu chuyện của công chúa và cuộc phiêu lưu làm cho tôi cảm thấy kỳ lạ. - Crystallize


Khi đặt câu hỏi một cách nghiêm túc, chúng ta nên tìm hiểu xem một đứa trẻ 6 tuổi nào có khả năng nhận thức, mặc dù phải thừa nhận rằng, một người quan tâm đến JavaScript không phải là điển hình.

Trên Phát triển tuổi thơ: 5 đến 7 năm  nó nói rằng:

Con của bạn sẽ có thể làm theo hướng dẫn hai bước. Ví dụ, nếu bạn nói với con bạn, "Đi vào nhà bếp và lấy cho tôi một cái túi rác" họ sẽ có thể nhớ hướng đó.

Chúng ta có thể sử dụng ví dụ này để giải thích các bao đóng, như sau:

Các nhà bếp là một đóng cửa có một biến địa phương, được gọi là trashBags. Có một chức năng bên trong nhà bếp được gọi là getTrashBag nhận được một túi rác và trả lại.

Chúng tôi có thể viết mã này bằng JavaScript như sau:

function makeKitchen () {
  var trashBags = ['A', 'B', 'C']; // only 3 at first

  return {
    getTrashBag: function() {
      return trashBags.pop();
    }
  };
}

var kitchen = makeKitchen();

kitchen.getTrashBag(); // returns trash bag C
kitchen.getTrashBag(); // returns trash bag B
kitchen.getTrashBag(); // returns trash bag A

Các điểm khác giải thích tại sao các bao đóng lại thú vị:

  • Mỗi lần makeKitchen() được gọi là, một đóng cửa mới được tạo ra với riêng biệt của nó trashBags.
  • Các trashBags biến là địa phương bên trong mỗi bếp và không thể truy cập bên ngoài, nhưng hàm bên trong trên getTrashBagtài sản không có quyền truy cập vào nó.
  • Mỗi cuộc gọi hàm tạo ra một đóng cửa, nhưng sẽ không cần phải giữ kín xung quanh trừ khi một hàm bên trong, có quyền truy cập vào bên trong của bao đóng, có thể được gọi từ bên ngoài bao đóng. Trả lại đối tượng bằng getTrashBag chức năng làm điều đó ở đây.

693



Trên thực tế, gây nhầm lẫn, hàm makeKitchen gọi điện là thực tế đóng cửa, không phải là đối tượng nhà bếp mà nó trả về. - dlaliberte
Có cách của tôi thông qua những người khác tôi tìm thấy câu trả lời này là cách dễ nhất để giải thích về những gì và tại sao closures.is. - Chetabahana
Quá nhiều thực đơn và món khai vị, không đủ thịt và khoai tây. Bạn có thể cải thiện câu trả lời đó chỉ với một câu ngắn như: "Một đóng là bối cảnh kín của một hàm, vì thiếu bất kỳ cơ chế phạm vi nào được cung cấp bởi các lớp." - Jacob


Người rơm

Tôi cần phải biết bao nhiêu lần một nút đã được nhấp và làm một cái gì đó trên mỗi nhấp chuột thứ ba ...

Giải pháp khá rõ ràng

// Declare counter outside event handler's scope
var counter = 0;
var element = document.getElementById('button');

element.addEventListener("click", function() {
  // Increment outside counter
  counter++;

  if (counter === 3) {
    // Do something every third time
    console.log("Third time's the charm!");

    // Reset counter
    counter = 0;
  }
});
<button id="button">Click Me!</button>

Bây giờ điều này sẽ làm việc, nhưng nó xâm phạm vào phạm vi bên ngoài bằng cách thêm một biến, có mục đích duy nhất là theo dõi số lượng. Trong một số trường hợp, điều này sẽ thích hợp hơn vì ứng dụng bên ngoài của bạn có thể cần truy cập vào thông tin này. Nhưng trong trường hợp này, chúng tôi chỉ thay đổi mọi hành vi của nhấp chuột thứ ba, vì vậy, nó thích hợp hơn để kèm theo chức năng này bên trong trình xử lý sự kiện.

Xem xét tùy chọn này

var element = document.getElementById('button');

element.addEventListener("click", (function() {
  // init the count to 0
  var count = 0;

  return function(e) { // <- This function becomes the click handler
    count++; //    and will retain access to the above `count`

    if (count === 3) {
      // Do something every third time
      console.log("Third time's the charm!");

      //Reset counter
      count = 0;
    }
  };
})());
<button id="button">Click Me!</button>

Lưu ý một vài điều ở đây.

Trong ví dụ trên, tôi đang sử dụng hành vi đóng của JavaScript. Hành vi này cho phép bất kỳ chức năng nào có quyền truy cập vào phạm vi mà nó được tạo, vô thời hạn. Để thực tế áp dụng điều này, tôi ngay lập tức gọi một hàm trả về một hàm khác, và vì hàm tôi đang quay lại có quyền truy cập vào biến số đếm nội bộ (vì hành vi đóng được giải thích ở trên) kết quả này trong phạm vi riêng để sử dụng bởi kết quả chức năng ... Không đơn giản như vậy? Hãy pha loãng nó đi ...

Một đóng cửa một dòng đơn giản

//          _______________________Immediately invoked______________________
//         |                                                                |
//         |        Scope retained for use      ___Returned as the____      |
//         |       only by returned function   |    value of func     |     |
//         |             |            |        |                      |     |
//         v             v            v        v                      v     v
var func = (function() { var a = 'val'; return function() { alert(a); }; })();

Tất cả các biến bên ngoài hàm trả về đều có sẵn cho hàm trả về, nhưng chúng không có sẵn trực tiếp đối tượng hàm trả về ...

func();  // Alerts "val"
func.a;  // Undefined

Hiểu rồi? Vì vậy, trong ví dụ chính của chúng tôi, biến số đếm được chứa trong phần đóng và luôn sẵn sàng cho trình xử lý sự kiện, do đó, nó giữ trạng thái của nó từ nhấp chuột để nhấp.

Ngoài ra, trạng thái biến riêng tư này là đầy đủ có thể truy cập, cho cả hai bài đọc và gán cho các biến phạm vi riêng của nó.

Có bạn đi; bây giờ bạn hoàn toàn đóng gói hành vi này.

Toàn bộ bài đăng trên blog (bao gồm các cân nhắc của jQuery)


527



Tôi không đồng ý với định nghĩa của bạn về việc đóng cửa là gì. Không có lý do gì mà nó phải tự gọi. Nó cũng là một chút đơn giản (và không chính xác) để nói rằng nó đã được "trả lại" (rất nhiều thảo luận về điều này trong các ý kiến ​​của câu trả lời hàng đầu cho câu hỏi này) - James Montagne
@ James thậm chí nếu bạn không đồng ý, ví dụ của anh ấy (và toàn bộ bài đăng) là một trong những điều tốt nhất tôi từng thấy. Trong khi câu hỏi không phải là cũ và giải quyết cho tôi, nó hoàn toàn xứng đáng là một +1. - e-satis
"Tôi cần biết số lần nút được nhấp vào và thực hiện điều gì đó trên mỗi nhấp chuột thứ ba ..." Điều này khiến tôi chú ý. Một trường hợp sử dụng và giải pháp cho thấy việc đóng cửa không phải là điều bí ẩn như thế và nhiều người trong chúng ta đã viết chúng nhưng không biết chính xác tên chính thức. - Chris22
Ví dụ điển hình bởi vì nó cho thấy rằng "đếm" trong ví dụ thứ 2 giữ lại giá trị "đếm" và không đặt lại thành 0 mỗi lần "phần tử" được nhấp vào. Rất thông tin! - Adam
+1 cho hành vi đóng cửa. Chúng ta có thể giới hạn không hành vi đóng cửa đến chức năng trong javascript hoặc khái niệm này cũng có thể được áp dụng cho các cấu trúc khác của ngôn ngữ? - Dziamid


Đóng cửa khó giải thích bởi vì chúng được sử dụng để làm cho một số công việc hành vi mà tất cả mọi người intuitively hy vọng để làm việc anyway. Tôi tìm cách tốt nhất để giải thích chúng (và cách mà tôi học được những gì họ làm) là để tưởng tượng tình hình mà không có họ:

    var bind = function(x) {
        return function(y) { return x + y; };
    }
    
    var plus5 = bind(5);
    console.log(plus5(3));

Điều gì sẽ xảy ra ở đây nếu JavaScript không biết bao đóng? Chỉ cần thay thế cuộc gọi trong dòng cuối cùng bằng phần thân phương thức của nó (về cơ bản là những gì các cuộc gọi hàm thực hiện) và bạn nhận được:

console.log(x + 3);

Bây giờ, định nghĩa của đâu x? Chúng tôi đã không xác định nó trong phạm vi hiện tại. Giải pháp duy nhất là để cho plus5  mang phạm vi của nó (hoặc đúng hơn là phạm vi của cha mẹ) xung quanh. Cách này, x được xác định rõ ràng và nó được ràng buộc với giá trị 5.


445



Tôi đồng ý. Cho các chức năng có ý nghĩa tên thay vì truyền thống "foobar" những người thân cũng giúp tôi rất nhiều. Ngữ nghĩa đếm. - Ishmael
vì vậy trong một ngôn ngữ giả, về cơ bản nó giống như alert(x+3, where x = 5). Các where x = 5 là đóng cửa. Tôi có đúng không? - Jus12
@ Jus12: chính xác. Đằng sau hậu trường, việc đóng cửa chỉ là một số không gian nơi các giá trị biến hiện tại (“ràng buộc”) được lưu trữ, như trong ví dụ của bạn. - Konrad Rudolph
Đây chính xác là một ví dụ làm cho nhiều người lầm tưởng rằng đó là giá trị được sử dụng trong hàm trả về, không phải là biến có thể thay đổi. Nếu nó đã được thay đổi thành "return x + = y", hoặc tốt hơn cả cái đó và hàm khác "x * = y", thì rõ ràng là không có gì đang được sao chép. Đối với những người sử dụng để ngăn xếp khung, hãy tưởng tượng sử dụng khung heap thay vào đó, có thể tiếp tục tồn tại sau khi hàm trả về. - Matt
@ Tôi không đồng ý. Một ví dụ là không phải nghĩa vụ phải ghi toàn bộ tài liệu. Nó có nghĩa là để được sự quy nạp và minh họa tính năng nổi bật của một khái niệm. OP yêu cầu một lời giải thích đơn giản (“cho một đứa trẻ sáu tuổi”). Lấy câu trả lời được chấp nhận: Nó hoàn toàn thất bại đưa ra một lời giải thích ngắn gọn, chính xác vì nó cố gắng hết sức. (Tôi đồng ý với bạn rằng đó là thuộc tính quan trọng của JavaScript ràng buộc là tham chiếu chứ không phải theo giá trị ... nhưng một lần nữa, giải thích thành công là một giải thích giảm đến mức tối thiểu.) - Konrad Rudolph


Đây là một nỗ lực để làm sáng tỏ một số (có thể) hiểu lầm về đóng cửa xuất hiện trong một số câu trả lời khác.

  • Việc đóng không chỉ được tạo khi bạn trả về hàm bên trong. Trong thực tế, hàm kèm theo không cần phải trả lại chút nào để đóng cửa của nó được tạo ra. Thay vào đó, bạn có thể gán hàm bên trong của mình cho một biến trong phạm vi bên ngoài, hoặc chuyển nó thành một đối số cho một hàm khác, nơi nó có thể được gọi ngay lập tức hoặc bất kỳ lúc nào sau đó. Do đó, có thể tạo ra hàm đóng kín ngay khi hàm kèm theo được gọi vì bất kỳ hàm bên trong nào cũng có quyền truy cập vào đóng cửa đó bất cứ khi nào hàm bên trong được gọi, trước hoặc sau hàm trả về.
  • Việc đóng không tham chiếu một bản sao của giá trị cũ các biến trong phạm vi của nó. Bản thân các biến là một phần của việc đóng, và vì vậy giá trị được nhìn thấy khi truy cập vào một trong các biến đó là giá trị mới nhất tại thời điểm nó được truy cập. Đây là lý do tại sao các hàm bên trong được tạo bên trong các vòng lặp có thể phức tạp, vì mỗi cái có quyền truy cập vào cùng các biến bên ngoài thay vì lấy một bản sao của các biến tại thời điểm hàm được tạo ra hoặc được gọi.
  • Các "biến" trong một bao đóng bao gồm bất kỳ hàm được đặt tên nào được khai báo trong hàm. Chúng cũng bao gồm các đối số của hàm. Một đóng cũng có quyền truy cập vào các biến đóng của nó có chứa, tất cả các con đường lên đến phạm vi toàn cầu.
  • Đóng cửa sử dụng bộ nhớ, nhưng chúng không gây rò rỉ bộ nhớ kể từ khi JavaScript tự làm sạch các cấu trúc tròn riêng của nó mà không được tham chiếu. Rò rỉ bộ nhớ Internet Explorer liên quan đến các bao đóng được tạo ra khi nó không ngắt kết nối các giá trị thuộc tính DOM tham chiếu đến các bao đóng, do đó duy trì các tham chiếu đến các cấu trúc vòng tròn có thể.

343



Nhân tiện, tôi đã thêm "câu trả lời" này bằng cách làm rõ để không trực tiếp giải quyết câu hỏi gốc. Thay vào đó, tôi hy vọng rằng bất kỳ câu trả lời đơn giản nào (đối với một đứa trẻ 6 tuổi) đều không đưa ra những khái niệm sai về chủ đề phức tạp này. Ví dụ. câu trả lời phổ biến trên wiki nói "Đóng cửa là khi bạn trả về hàm bên trong." Ngoài sai ngữ pháp, đó là sai về mặt kỹ thuật. - dlaliberte
James, tôi nói rằng việc đóng cửa là "có thể" được tạo ra tại thời điểm gọi hàm kèm theo bởi vì nó là hợp lý rằng việc triển khai có thể trì hoãn việc đóng cửa cho đến một thời gian sau đó, khi quyết định đóng cửa là hoàn toàn cần thiết. Nếu không có hàm bên trong được định nghĩa trong hàm kèm theo, thì không cần đóng cửa. Vì vậy, có lẽ nó có thể đợi cho đến khi hàm bên trong đầu tiên được tạo ra để sau đó tạo ra một bao đóng ngoài ngữ cảnh cuộc gọi của hàm bao hàm. - dlaliberte
@ Beetroot-Beetroot Giả sử chúng ta có một hàm bên trong được truyền cho một hàm khác khi nó được sử dụng trước hàm bên ngoài trả về, và giả sử chúng ta cũng trả về cùng hàm bên trong từ hàm bên ngoài. Nó giống hệt chức năng trong cả hai trường hợp, nhưng bạn đang nói rằng trước khi hàm ngoài trả về, hàm bên trong được "ràng buộc" với ngăn xếp cuộc gọi, trong khi sau khi nó trả về, hàm bên trong đột nhiên bị ràng buộc với một đóng. Nó hoạt động giống nhau trong cả hai trường hợp; ngữ nghĩa là giống hệt nhau, vậy bạn không chỉ nói về chi tiết thực hiện? - dlaliberte
@ Củ cải đường-Củ cải đường, cảm ơn phản hồi của bạn, và tôi vui vì tôi đã cho bạn suy nghĩ. Tôi vẫn không thấy bất kỳ sự khác biệt ngữ nghĩa giữa bối cảnh trực tiếp của hàm bên ngoài và bối cảnh tương tự khi nó trở thành một đóng cửa khi hàm trả về (nếu tôi hiểu định nghĩa của bạn). Hàm bên trong không quan tâm. Bộ sưu tập rác không quan tâm vì hàm bên trong duy trì một tham chiếu đến ngữ cảnh / đóng cửa theo cách, và người gọi hàm bên ngoài chỉ giảm tham chiếu của nó xuống ngữ cảnh cuộc gọi. Nhưng nó gây nhầm lẫn với mọi người, và có lẽ tốt hơn để gọi nó là ngữ cảnh cuộc gọi. - dlaliberte
Bài viết đó rất khó đọc, nhưng tôi nghĩ nó thực sự hỗ trợ những gì tôi đang nói. Nó nói: "Một đóng được tạo thành bằng cách trả về một đối tượng hàm [...] hoặc bằng cách gán trực tiếp một tham chiếu tới đối tượng hàm như vậy, ví dụ, một biến toàn cầu." Tôi không có nghĩa là GC không liên quan. Thay vào đó, vì GC, và bởi vì hàm bên trong được gắn vào ngữ cảnh cuộc gọi của hàm bên ngoài (hoặc [[scope]] như bài báo nói), thì việc gọi hàm bên ngoài trả về vì ràng buộc với bên trong không quan trọng chức năng là điều quan trọng. - dlaliberte


OK, người hâm mộ đóng cửa 6 tuổi. Bạn có muốn nghe ví dụ đơn giản nhất về đóng cửa không?

Hãy tưởng tượng tình hình tiếp theo: một người lái xe đang ngồi trong xe hơi. Chiếc xe đó ở trong một chiếc máy bay. Máy bay đang ở trong sân bay. Khả năng của người lái xe để truy cập những thứ bên ngoài chiếc xe của mình, nhưng bên trong máy bay, ngay cả khi chiếc máy bay đó rời khỏi sân bay, là một sự đóng cửa. Đó là nó. Khi bạn bật 27, hãy nhìn vào giải thích chi tiết hơn hoặc tại ví dụ bên dưới.

Đây là cách tôi có thể chuyển đổi câu chuyện máy bay của mình thành mã.

var plane = function (defaultAirport) {

    var lastAirportLeft = defaultAirport;

    var car = {
        driver: {
            startAccessPlaneInfo: function () {
                setInterval(function () {
                    console.log("Last airport was " + lastAirportLeft);
                }, 2000);
            }
        }
    };
    car.driver.startAccessPlaneInfo();

    return {
        leaveTheAirport: function (airPortName) {
            lastAirportLeft = airPortName;
        }
    }
}("Boryspil International Airport");

plane.leaveTheAirport("John F. Kennedy");

331



Cũng chơi và trả lời các poster ban đầu. Tôi nghĩ đây là câu trả lời hay nhất. Tôi sẽ sử dụng hành lý theo cách tương tự: hãy tưởng tượng bạn đến nhà bà và bạn đóng gói nintendo DS của bạn với thẻ trò chơi trong trường hợp của bạn, nhưng sau đó đóng gói các trường hợp bên trong ba lô của bạn và cũng đặt thẻ trò chơi trong túi ba lô của bạn, và THEN bạn đặt toàn bộ thứ trong một chiếc va li lớn với nhiều thẻ trò chơi hơn trong túi của chiếc vali. Khi bạn đến nhà của bà, bạn có thể chơi bất kỳ trò chơi nào trên DS của bạn miễn là tất cả các trường hợp bên ngoài đều mở. Hoặc một cái gì đó để có hiệu lực. - slartibartfast


A đóng cửa giống như một vật thể. Nó được khởi tạo bất cứ khi nào bạn gọi một hàm.

Phạm vi của một đóng cửa trong JavaScript là từ vựng, có nghĩa là mọi thứ được chứa trong hàm đóng cửa thuộc về, có quyền truy cập vào bất kỳ biến nào trong đó.

Một biến được chứa trong đóng cửa nếu bạn

  1. gán nó với var foo=1; hoặc là
  2. chỉ viết var foo;

Nếu hàm bên trong (hàm chứa bên trong một hàm khác) truy cập một biến như vậy mà không định nghĩa nó trong phạm vi riêng của nó bằng var, nó sửa đổi nội dung của biến ở bên ngoài đóng cửa.

A đóng cửa outlives thời gian chạy của hàm sinh ra nó. Nếu các chức năng khác làm cho nó ra khỏi đóng cửa / phạm vitrong đó chúng được định nghĩa (ví dụ như giá trị trả lại), chúng sẽ tiếp tục tham chiếu đóng cửa.

Thí dụ

 function example(closure) {
   // define somevariable to live in the closure of example
   var somevariable = 'unchanged';

   return {
     change_to: function(value) {
       somevariable = value;
     },
     log: function(value) {
       console.log('somevariable of closure %s is: %s',
         closure, somevariable);
     }
   }
 }

 closure_one = example('one');
 closure_two = example('two');

 closure_one.log();
 closure_two.log();
 closure_one.change_to('some new value');
 closure_one.log();
 closure_two.log();

Đầu ra

somevariable of closure one is: unchanged
somevariable of closure two is: unchanged
somevariable of closure one is: some new value
somevariable of closure two is: unchanged

320



Wow, không bao giờ biết bạn có thể sử dụng thay thế chuỗi trong console.log như thế. Nếu ai khác quan tâm thì có nhiều hơn: developer.mozilla.org/en-US/docs/DOM/… - Flash
Các biến nằm trong danh sách tham số của hàm cũng là một phần của quá trình đóng (ví dụ: không chỉ giới hạn ở var). - Thomas Eding


Tôi đã viết một bài đăng trên blog trong khi lại giải thích về việc đóng cửa. Đây là những gì tôi đã nói về việc đóng cửa về mặt tại sao bạn muốn một cái.

Đóng cửa là một cách để cho một hàm   có biến cố định, riêng tư - -   đó là, các biến chỉ có một   chức năng biết về, nơi nó có thể   theo dõi thông tin từ những lần trước   nó đã được chạy.

Theo nghĩa đó, chúng cho phép một hàm hoạt động giống như một đối tượng có các thuộc tính riêng.

Toàn bộ bài đăng:

Vì vậy, những thingys đóng cửa là gì?


215



Vì vậy, lợi ích chính của bao đóng có thể được nhấn mạnh với ví dụ này? Nói rằng tôi có một chức năng emailError (sendToAddress, errorString) sau đó tôi có thể nói devError = emailError("devinrhode2@googmail.com", errorString) và sau đó có phiên bản tùy chỉnh của riêng tôi của một chức năng email được chia sẻError? - Devin G Rhode
Sau khi xúc động theo cách của tôi thông qua một toàn bộ rất nhiều 'chia tay, tôi bắt đầu cuối cùng đã hiểu những gì họ đã cho. Tôi tự nhủ "oh, những biến riêng tư của nó trong một vật thể?" và bam. Đây là câu trả lời tiếp theo tôi đọc. - Mixologic