Đóng cửa là gì?


155

Thỉnh thoảng tôi thấy "đóng cửa" được đề cập và tôi đã cố gắng tìm kiếm nhưng Wiki không đưa ra lời giải thích mà tôi hiểu. ai đó có thể giúp tôi ra ở đây?


Nếu bạn biết Java / C #, hy vọng liên kết này sẽ giúp- http://www.developerfusion.com/article/8251/the-beauty-of-closures/
Gulshan

1
Đóng cửa là khó hiểu. Bạn nên thử nhấp vào tất cả các liên kết trong câu đầu tiên của bài viết Wikipedia đó và hiểu những bài viết đó trước.
Zach


3
Sự khác biệt cơ bản giữa đóng cửa và một lớp mặc dù là gì? Được rồi, một lớp chỉ có một phương thức công khai.
biziclop

5
@biziclop: Bạn có thể mô phỏng việc đóng cửa với một lớp (đó là điều mà các nhà phát triển Java phải làm). Nhưng chúng thường ít dài dòng hơn để tạo và bạn không phải quản lý thủ công những gì bạn đang chú ý xung quanh. (Những người nói dối khó tính hỏi một câu hỏi tương tự, nhưng có thể đi đến kết luận khác - rằng hỗ trợ OO ở cấp độ ngôn ngữ là không cần thiết khi bạn đóng cửa).

Câu trả lời:


141

(Tuyên bố miễn trừ trách nhiệm: đây là một lời giải thích cơ bản; theo như định nghĩa, tôi đơn giản hóa một chút)

Cách đơn giản nhất để nghĩ về việc đóng là một hàm có thể được lưu trữ dưới dạng một biến (được gọi là "hàm hạng nhất"), có khả năng đặc biệt để truy cập các biến khác cục bộ vào phạm vi mà nó được tạo.

Ví dụ (JavaScript):

var setKeyPress = function(callback) {
    document.onkeypress = callback;
};

var initialize = function() {
    var black = false;

    document.onclick = function() {
        black = !black;
        document.body.style.backgroundColor = black ? "#000000" : "transparent";
    }

    var displayValOfBlack = function() {
        alert(black);
    }

    setKeyPress(displayValOfBlack);
};

initialize();

Các chức năng 1 được gán cho document.onclickdisplayValOfBlacklà đóng cửa. Bạn có thể thấy rằng cả hai đều tham chiếu biến boolean black, nhưng biến đó được gán bên ngoài hàm. Vì blackcục bộ của phạm vi nơi hàm được xác định , con trỏ tới biến này được giữ nguyên.

Nếu bạn đặt nó trong một trang HTML:

  1. Nhấn vào đây để đổi thành màu đen
  2. Nhấn [enter] để xem "true"
  3. Nhấp lại, thay đổi trở lại màu trắng
  4. Nhấn [enter] để xem "false"

Điều này chứng tỏ rằng cả hai đều có quyền truy cập như nhau black và có thể được sử dụng để lưu trữ trạng thái mà không có bất kỳ đối tượng trình bao bọc nào.

Cuộc gọi đến setKeyPresslà để chứng minh làm thế nào một chức năng có thể được thông qua giống như bất kỳ biến nào. Các phạm vi bảo quản trong việc đóng cửa vẫn là một trong những nơi hàm được xác định.

Các bao đóng thường được sử dụng như các trình xử lý sự kiện, đặc biệt là trong JavaScript và ActionScript. Việc sử dụng tốt các bao đóng sẽ giúp bạn ngầm liên kết các biến với các trình xử lý sự kiện mà không phải tạo một trình bao bọc đối tượng. Tuy nhiên, việc sử dụng bất cẩn sẽ dẫn đến rò rỉ bộ nhớ (chẳng hạn như khi một trình xử lý sự kiện không được sử dụng nhưng được bảo quản là điều duy nhất để giữ các đối tượng lớn trong bộ nhớ, đặc biệt là các đối tượng DOM, ngăn chặn việc thu gom rác).


1: Trên thực tế, tất cả các chức năng trong JavaScript là đóng cửa.


3
Khi tôi đang đọc câu trả lời của bạn, tôi cảm thấy một bóng đèn bật trong tâm trí của tôi. Nhiều đánh giá cao! :)
Jay

1
blackđược khai báo bên trong một hàm, liệu nó có bị phá hủy khi ngăn xếp thư giãn không ...?
gablin

1
@gablin, đó là những gì độc đáo về các ngôn ngữ có sự đóng cửa. Tất cả các ngôn ngữ có bộ sưu tập rác hoạt động theo cùng một cách - khi không có thêm tài liệu tham khảo nào cho một đối tượng, nó có thể bị hủy. Bất cứ khi nào một hàm được tạo trong JS, phạm vi cục bộ được liên kết với hàm đó cho đến khi hàm đó bị hủy.
Nicole

2
@gablin, đó là một câu hỏi hay. Tôi không nghĩ họ không thể & mdash; nhưng tôi chỉ đưa ra bộ sưu tập rác vì đó là những gì mà JS sử dụng và đó là những gì bạn dường như đang đề cập đến khi bạn nói "Vì blackđược khai báo bên trong một hàm, nên nó sẽ không bị phá hủy". Cũng nhớ rằng nếu bạn khai báo một đối tượng trong một hàm và sau đó gán nó cho một biến sống ở một nơi khác, thì đối tượng đó được giữ nguyên vì có các tham chiếu khác đến nó.
Nicole

1
Objective-C (và C theo clang) hỗ trợ các khối, về cơ bản là đóng, không có bộ sưu tập rác. Nó đòi hỏi hỗ trợ thời gian chạy và một số can thiệp thủ công xung quanh việc quản lý bộ nhớ.
quixoto

68

Một đóng cửa về cơ bản chỉ là một cách khác nhau để nhìn vào một đối tượng. Một đối tượng là dữ liệu có một hoặc nhiều chức năng ràng buộc với nó. Một bao đóng là một hàm có một hoặc nhiều biến ràng buộc với nó. Hai cái cơ bản là giống hệt nhau, ở mức độ thực hiện ít nhất. Sự khác biệt thực sự là nơi họ đến từ.

Trong lập trình hướng đối tượng, bạn khai báo một lớp đối tượng bằng cách xác định các biến thành viên của nó và các phương thức của nó (các hàm thành viên) ở phía trước, và sau đó bạn tạo các thể hiện của lớp đó. Mỗi phiên bản đi kèm với một bản sao của dữ liệu thành viên, được khởi tạo bởi hàm tạo. Sau đó, bạn có một biến của một loại đối tượng và chuyển nó xung quanh dưới dạng một phần dữ liệu, vì trọng tâm là bản chất của nó là dữ liệu.

Mặt khác, trong một bao đóng, đối tượng không được xác định từ trước như một lớp đối tượng hoặc được khởi tạo thông qua một lệnh gọi hàm tạo trong mã của bạn. Thay vào đó, bạn viết bao đóng như là một hàm bên trong của hàm khác. Việc đóng có thể tham chiếu đến bất kỳ biến cục bộ nào của hàm ngoài và trình biên dịch phát hiện điều đó và di chuyển các biến này từ không gian ngăn xếp của hàm ngoài sang khai báo đối tượng ẩn của bao đóng. Sau đó, bạn có một biến của kiểu đóng, và mặc dù về cơ bản nó là một đối tượng dưới mui xe, bạn chuyển nó xung quanh như một tham chiếu hàm, bởi vì trọng tâm là bản chất của nó như là một hàm.


3
+1: Câu trả lời hay. Bạn có thể thấy một bao đóng là một đối tượng chỉ có một phương thức và một đối tượng tùy ý là một tập hợp các bao đóng trên một số dữ liệu cơ bản phổ biến (các biến thành viên của đối tượng). Tôi nghĩ hai quan điểm này khá đối xứng.
Giorgio

3
Câu trả lời rất hay. Nó thực sự giải thích cái nhìn sâu sắc của việc đóng cửa.
RoboAlex

1
@Mason Wheeler: Dữ liệu đóng được lưu trữ ở đâu? Trong ngăn xếp như một chức năng? Hoặc trong đống như một đối tượng?
RoboAlex

1
@RoboAlex: Trong heap, vì đó là một đối tượng trông giống như một hàm.
Mason Wheeler

1
@RoboAlex: Trường hợp đóng và dữ liệu đã chụp được lưu trữ tùy thuộc vào việc thực hiện. Trong C ++, nó có thể được lưu trữ trong heap hoặc trên stack.
Giorgio

29

Thuật ngữ đóng xuất phát từ thực tế là một đoạn mã (khối, hàm) có thể có các biến miễn phí được đóng (tức là bị ràng buộc với một giá trị) bởi môi trường trong đó khối mã được xác định.

Lấy ví dụ về định nghĩa hàm Scala:

def addConstant(v: Int): Int = v + k

Trong thân hàm có hai tên (biến) vkchỉ ra hai giá trị nguyên. Tên vbị ràng buộc bởi vì nó được khai báo như là một đối số của hàm addConstant(bằng cách nhìn vào khai báo hàm chúng ta biết rằng vsẽ được gán một giá trị khi hàm được gọi). Tên knày là hàm wrt miễn phí addConstantvì hàm này không chứa manh mối nào về giá trị nào kđược ràng buộc với (và làm thế nào).

Để đánh giá một cuộc gọi như:

val n = addConstant(10)

chúng ta phải gán kmột giá trị, điều này chỉ có thể xảy ra nếu tên kđược xác định trong ngữ cảnh addConstantđược xác định. Ví dụ:

def increaseAll(values: List[Int]): List[Int] =
{
  val k = 2

  def addConstant(v: Int): Int = v + k

  values.map(addConstant)
}

Bây giờ chúng ta đã xác định addConstanttrong một bối cảnh kđược xác định, addConstantđã trở thành một bao đóng vì tất cả các biến tự do của nó hiện đang bị đóng (ràng buộc với một giá trị): addConstantcó thể được gọi và chuyển xung quanh như thể nó là một hàm. Lưu ý biến tự do kbị ràng buộc với một giá trị khi đóng được xác định , trong khi đó biến đối số vbị ràng buộc khi đóng được gọi .

Vì vậy, một bao đóng về cơ bản là một hàm hoặc khối mã có thể truy cập các giá trị không cục bộ thông qua các biến miễn phí của nó sau khi các giá trị này bị ràng buộc bởi bối cảnh.

Trong nhiều ngôn ngữ, nếu bạn chỉ sử dụng bao đóng một lần, bạn có thể ẩn danh , ví dụ:

def increaseAll(values: List[Int]): List[Int] =
{
  val k = 2

  values.map(v => v + k)
}

Lưu ý rằng một hàm không có biến miễn phí là trường hợp đặc biệt của bao đóng (với một tập hợp các biến miễn phí trống). Tương tự, một hàm ẩn danh là một trường hợp đặc biệt của một bao đóng ẩn danh , tức là một hàm ẩn danh là một bao ẩn danh không có biến miễn phí.


Điều này rất tốt với các công thức đóng và mở trong logic. Cảm ơn câu trả lời của bạn.
RainDoctor

@RainDoctor: Các biến miễn phí được định nghĩa trong các công thức logic và trong các biểu thức tính toán lambda theo cách tương tự: lambda trong biểu thức lambda hoạt động giống như một bộ định lượng trong công thức logic viết các biến tự do / ràng buộc.
Giorgio

9

Một lời giải thích đơn giản trong JavaScript:

var closure_example = function() {
    var closure = 0;
    // after first iteration the value will not be erased from the memory
    // because it is bound with the returned alertValue function.
    return {
        alertValue : function() {
            closure++;
            alert(closure);
        }
    };
};
closure_example();

alert(closure)sẽ sử dụng giá trị được tạo trước đó của closure. Không alertValuegian tên của hàm được trả về sẽ được kết nối với không gian tên trong đó closurebiến nằm trong đó . Khi bạn xóa toàn bộ hàm, giá trị của closurebiến sẽ bị xóa, nhưng cho đến lúc đó, alertValuehàm sẽ luôn có thể đọc / ghi giá trị của biến closure.

Nếu bạn chạy mã này, lần lặp đầu tiên sẽ gán giá trị 0 cho closurebiến và viết lại hàm cho:

var closure_example = function(){
    alertValue : function(){
        closure++;
        alert(closure);
    }       
}

Và bởi vì alertValuecần biến cục bộ closuređể thực thi hàm, nó liên kết chính nó với giá trị của biến cục bộ được gán trước đó closure.

Và bây giờ mỗi khi bạn gọi closure_examplehàm, nó sẽ ghi giá trị tăng của closurebiến vì alert(closure)bị ràng buộc.

closure_example.alertValue()//alerts value 1 
closure_example.alertValue()//alerts value 2 
closure_example.alertValue()//alerts value 3
//etc. 

cảm ơn, tôi đã không kiểm tra mã =) bây giờ mọi thứ đều ổn.
Muha

5

Một "đóng cửa", về bản chất, một số trạng thái cục bộ và một số mã, được kết hợp thành một gói. Thông thường, trạng thái cục bộ xuất phát từ một phạm vi (từ vựng) xung quanh và mã (về cơ bản) là một hàm bên trong sau đó được trả về bên ngoài. Việc đóng sau đó là sự kết hợp của các biến được bắt mà hàm bên trong nhìn thấy và mã của hàm bên trong.

Thật không may, một trong những điều đó, thật không may, hơi khó để giải thích, do không quen thuộc.

Một từ tương tự tôi đã sử dụng thành công trong quá khứ là "hãy tưởng tượng chúng ta có một thứ mà chúng ta gọi là" cuốn sách ", trong phòng đóng cửa," cuốn sách "là bản sao ở đó, ở góc, của TAOCP, nhưng trên cái bàn đóng , đó là bản sao của một cuốn sách Tập tin Dresden. Vì vậy, tùy thuộc vào việc bạn đóng cửa ở đâu, mã 'đưa cho tôi cuốn sách' dẫn đến những điều khác nhau xảy ra. "


Bạn đã quên điều này: vi.wikipedia.org/wiki/Clenses_(computer_programming) trong câu trả lời của bạn.
S.Lott

3
Không, tôi đồng ý chọn không đóng trang đó.
Vatine

"Trạng thái và chức năng.": Hàm C có staticbiến cục bộ có thể được coi là đóng không? Do đóng cửa trong Haskell liên quan đến nhà nước?
Giorgio

2
@Giorgio Đóng trong Haskell (tôi tin) gần với các đối số trong phạm vi từ vựng mà chúng được xác định, vì vậy, tôi sẽ nói "có" (mặc dù tôi không quen với Haskell). Hàm AC với một biến tĩnh là tốt nhất là một bao đóng rất hạn chế (bạn thực sự muốn có thể tạo nhiều bao đóng từ một hàm duy nhất, với một staticbiến cục bộ, bạn có chính xác một).
Vatine

Tôi đã hỏi câu hỏi này về mục đích bởi vì tôi nghĩ rằng hàm C có biến tĩnh không phải là bao đóng: biến tĩnh được định nghĩa cục bộ và chỉ được biết bên trong bao đóng, nó không truy cập vào môi trường. Ngoài ra, tôi không chắc chắn 100% nhưng tôi sẽ xây dựng câu lệnh của bạn theo cách khác: bạn sử dụng cơ chế đóng để tạo các hàm khác nhau (hàm là định nghĩa đóng + ràng buộc cho các biến miễn phí của nó).
Giorgio

5

Thật khó để định nghĩa đóng cửa là gì mà không xác định khái niệm 'trạng thái'.

Về cơ bản, trong một ngôn ngữ có phạm vi từ vựng đầy đủ coi các chức năng là giá trị hạng nhất, điều gì đó đặc biệt xảy ra. Nếu tôi phải làm một cái gì đó như:

function foo(x)
return x
end

x = foo

Biến xkhông chỉ tham chiếu function foo()mà còn tham chiếu trạng tháifoo còn lại trong lần cuối cùng trả về. Phép thuật thực sự xảy ra khi foocó các chức năng khác được xác định rõ hơn trong phạm vi của nó; nó giống như môi trường nhỏ của chính nó (giống như 'thông thường', chúng tôi xác định các chức năng trong môi trường toàn cầu).

Về mặt chức năng, nó có thể giải quyết nhiều vấn đề tương tự như từ khóa 'tĩnh' của C ++ (C?), Giữ lại trạng thái của một biến cục bộ trong nhiều lệnh gọi hàm; tuy nhiên, nó giống như áp dụng cùng một nguyên tắc (biến tĩnh) cho một hàm, vì các hàm là các giá trị hạng nhất; bao đóng thêm hỗ trợ cho toàn bộ trạng thái của chức năng được lưu (không liên quan gì đến các hàm tĩnh của C ++).

Việc coi các hàm là các giá trị của lớp đầu tiên và thêm hỗ trợ cho các bao đóng cũng có nghĩa là bạn có thể có nhiều hơn một thể hiện của cùng một hàm trong bộ nhớ (tương tự như các lớp). Điều này có nghĩa là bạn có thể sử dụng lại cùng một mã mà không phải đặt lại trạng thái của hàm, như được yêu cầu khi xử lý các biến tĩnh C ++ bên trong một hàm (có thể sai về điều này?).

Đây là một số thử nghiệm về hỗ trợ đóng cửa của Lua.

--Closure testing
--By Trae Barlow
--

function myclosure()
    print(pvalue)--nil
    local pvalue = pvalue or 10
    return function()
        pvalue = pvalue + 10 --20, 31, 42, 53(53 never printed)
        print(pvalue)
        pvalue = pvalue + 1 --21, 32, 43(pvalue state saved through multiple calls)
        return pvalue
    end
end

x = myclosure() --x now references anonymous function inside myclosure()

x()--nil, 20
x() --21, 31
x() --32, 42
    --43, 53 -- if we iterated x() again

các kết quả:

nil
20
31
42

Nó có thể trở nên khó khăn và có thể thay đổi từ ngôn ngữ này sang ngôn ngữ khác, nhưng dường như trong Lua rằng bất cứ khi nào một chức năng được thực thi, trạng thái của nó được đặt lại. Tôi nói điều này bởi vì các kết quả từ mã ở trên sẽ khác nếu chúng ta truy cập trực tiếp vào myclosurehàm / trạng thái (thay vì thông qua hàm ẩn danh mà nó trả về), như pvaluesẽ được đặt lại về 10; nhưng nếu chúng ta truy cập trạng thái của myclenses thông qua x (hàm ẩn danh), bạn có thể thấy nó pvaluecòn sống và ở đâu đó trong bộ nhớ. Tôi nghi ngờ có một chút nữa cho nó, có lẽ ai đó có thể giải thích rõ hơn về bản chất của việc thực hiện.

Tái bút: Tôi không biết một chút về C ++ 11 (khác với những gì trong các phiên bản trước) vì vậy hãy lưu ý rằng đây không phải là sự so sánh giữa các lần đóng trong C ++ 11 và Lua. Ngoài ra, tất cả các 'đường được vẽ' từ Lua đến C ++ đều giống nhau vì các biến tĩnh và các bao đóng không giống nhau 100%; ngay cả khi đôi khi chúng được sử dụng để giải quyết các vấn đề tương tự.

Điều tôi không chắc chắn là, trong ví dụ mã ở trên, liệu hàm ẩn danh hay hàm bậc cao hơn có được coi là đóng không?


4

Một bao đóng là một chức năng có trạng thái liên quan:

Trong perl bạn tạo các bao đóng như thế này:

#!/usr/bin/perl

# This function creates a closure.
sub getHelloPrint
{
    # Bind state for the function we are returning.
    my ($first) = @_;a

    # The function returned will have access to the variable $first
    return sub { my ($second) = @_; print  "$first $second\n"; };
}

my $hw = getHelloPrint("Hello");
my $gw = getHelloPrint("Goodby");

&$hw("World"); // Print Hello World
&$gw("World"); // PRint Goodby World

Nếu chúng ta nhìn vào chức năng mới được cung cấp với C ++.
Nó cũng cho phép bạn liên kết trạng thái hiện tại với đối tượng:

#include <string>
#include <iostream>
#include <functional>


std::function<void(std::string const&)> getLambda(std::string const& first)
{
    // Here we bind `first` to the function
    // The second parameter will be passed when we call the function
    return [first](std::string const& second) -> void
    {   std::cout << first << " " << second << "\n";
    };
}

int main(int argc, char* argv[])
{
    auto hw = getLambda("Hello");
    auto gw = getLambda("GoodBye");

    hw("World");
    gw("World");
}

2

Hãy xem xét một chức năng đơn giản:

function f1(x) {
    // ... something
}

Hàm này được gọi là hàm cấp cao nhất vì nó không được lồng trong bất kỳ hàm nào khác. Mỗi hàm JavaScript liên kết với chính nó một danh sách các đối tượng được gọi là "Chuỗi phạm vi" . Chuỗi phạm vi này là một danh sách các đối tượng được sắp xếp. Mỗi đối tượng này định nghĩa một số biến.

Trong các hàm cấp cao nhất, chuỗi phạm vi bao gồm một đối tượng duy nhất là đối tượng toàn cục. Ví dụ, hàm f1trên có một chuỗi phạm vi có một đối tượng duy nhất trong đó xác định tất cả các biến toàn cục. (lưu ý rằng thuật ngữ "đối tượng" ở đây không có nghĩa là đối tượng JavaScript, nó chỉ là một đối tượng được xác định triển khai hoạt động như một thùng chứa biến, trong đó JavaScript có thể "tra cứu" các biến.)

Khi hàm này được gọi, JavaScript sẽ tạo ra một thứ gọi là "đối tượng kích hoạt" và đặt nó ở đầu chuỗi phạm vi. Đối tượng này chứa tất cả các biến cục bộ (ví dụ xở đây). Do đó bây giờ chúng ta có hai đối tượng trong chuỗi phạm vi: đầu tiên là đối tượng kích hoạt và bên dưới nó là đối tượng toàn cầu.

Lưu ý rất cẩn thận rằng hai đối tượng được đưa vào chuỗi phạm vi tại thời điểm KHÁC BIỆT. Đối tượng toàn cục được đặt khi hàm được xác định (nghĩa là khi JavaScript phân tích hàm và tạo đối tượng hàm) và đối tượng kích hoạt sẽ nhập khi hàm được gọi.

Vì vậy, bây giờ chúng ta biết điều này:

  • Mỗi hàm có một chuỗi phạm vi liên quan đến nó
  • Khi chức năng được xác định (khi đối tượng chức năng được tạo), JavaScript sẽ lưu chuỗi phạm vi với chức năng đó
  • Đối với các hàm cấp cao nhất, chuỗi phạm vi chỉ chứa đối tượng toàn cục tại thời điểm xác định hàm và thêm một đối tượng kích hoạt bổ sung trên đầu tại thời điểm gọi

Tình huống trở nên thú vị khi chúng ta xử lý các hàm lồng nhau. Vì vậy, hãy tạo một:

function f1(x) {

    function f2(y) {
        // ... something
    }

}

Khi f1được định nghĩa, chúng ta sẽ có một chuỗi phạm vi cho nó chỉ chứa đối tượng toàn cục.

Bây giờ khi f1được gọi, chuỗi phạm vi của f1đối tượng kích hoạt. Đối tượng kích hoạt này chứa biến xvà biến f2là một hàm. Và, lưu ý rằng f2đang được xác định. Do đó, tại thời điểm này, JavaScript cũng lưu một chuỗi phạm vi mới cho f2. Chuỗi phạm vi được lưu cho chức năng bên trong này là chuỗi phạm vi hiện tại có hiệu lực. Chuỗi phạm vi hiện tại có hiệu lực là của f1. Do đó f2's chuỗi phạm vi là f1của hiện tại chuỗi phạm vi - trong đó có các đối tượng hoạt hóa f1và đối tượng toàn cầu.

Khi f2được gọi, nó sẽ chứa đối tượng kích hoạt của riêng nó y, được thêm vào chuỗi phạm vi của nó đã chứa đối tượng kích hoạt f1và đối tượng toàn cục.

Nếu có một hàm lồng nhau khác được xác định bên trong f2, chuỗi phạm vi của nó sẽ chứa ba đối tượng tại thời điểm xác định (2 đối tượng kích hoạt của hai hàm ngoài và đối tượng toàn cục) và 4 tại thời điểm gọi.

Vì vậy, bây giờ chúng tôi hiểu cách chuỗi phạm vi hoạt động nhưng chúng tôi chưa nói về việc đóng cửa.

Sự kết hợp giữa một đối tượng hàm và một phạm vi (một tập hợp các ràng buộc biến) trong đó các biến của hàm được giải quyết được gọi là một bao đóng trong tài liệu khoa học máy tính - JavaScript hướng dẫn dứt khoát của David Flanagan

Hầu hết các hàm được gọi bằng cách sử dụng cùng một chuỗi phạm vi có hiệu lực khi hàm được xác định và thực sự không có vấn đề gì về việc đóng cửa có liên quan. Việc đóng cửa trở nên thú vị khi chúng được gọi theo một chuỗi phạm vi khác với chuỗi có hiệu lực khi chúng được xác định. Điều này xảy ra phổ biến nhất khi một đối tượng hàm lồng nhau được trả về từ hàm được xác định.

Khi hàm trả về, đối tượng kích hoạt đó sẽ bị xóa khỏi chuỗi phạm vi. Nếu không có các hàm lồng nhau, sẽ không có thêm các tham chiếu đến đối tượng kích hoạt và nó sẽ được thu gom rác. Nếu có các hàm lồng nhau được xác định, thì mỗi hàm đó có tham chiếu đến chuỗi phạm vi và chuỗi phạm vi đó đề cập đến đối tượng kích hoạt.

Tuy nhiên, nếu các đối tượng hàm lồng nhau đó vẫn nằm trong hàm ngoài của chúng, thì chính chúng sẽ là rác được thu thập, cùng với đối tượng kích hoạt mà chúng đã đề cập. Nhưng nếu hàm định nghĩa một hàm lồng nhau và trả về nó hoặc lưu trữ nó vào một thuộc tính ở đâu đó, thì sẽ có một tham chiếu bên ngoài đến hàm lồng nhau. Nó sẽ không được thu gom rác và đối tượng kích hoạt mà nó đề cập đến sẽ không phải là rác được thu thập.

Trong ví dụ trên của chúng tôi, chúng tôi không trở về f2từ f1đó, do đó, khi có lệnh gọi f1trở lại, đối tượng kích hoạt của nó sẽ bị xóa khỏi chuỗi phạm vi và rác được thu thập. Nhưng nếu chúng ta có một cái gì đó như thế này:

function f1(x) {

    function f2(y) {
        // ... something
    }

    return f2;
}

Ở đây, trả về f2sẽ có một chuỗi phạm vi sẽ chứa đối tượng kích hoạt f1và do đó nó sẽ không được thu gom rác. Tại thời điểm này, nếu chúng tôi gọi f2, nó sẽ có thể truy cập vào f1biến của xchúng ngay cả khi chúng tôi không tham gia f1.

Do đó, chúng ta có thể thấy rằng một hàm giữ chuỗi phạm vi của nó với nó và với chuỗi phạm vi có tất cả các đối tượng kích hoạt của các hàm bên ngoài. Đây là bản chất của đóng cửa. Chúng tôi nói rằng các hàm trong JavaScript là "phạm vi từ vựng" , nghĩa là chúng lưu phạm vi hoạt động khi chúng được định nghĩa trái ngược với phạm vi hoạt động khi chúng được gọi.

Có một số kỹ thuật lập trình mạnh mẽ liên quan đến việc đóng cửa như xấp xỉ các biến riêng tư, lập trình hướng sự kiện, ứng dụng một phần , v.v.

Cũng lưu ý rằng tất cả điều này áp dụng cho tất cả các ngôn ngữ hỗ trợ đóng cửa. Ví dụ: PHP (5.3+), Python, Ruby, v.v.


-1

Một bao đóng là một tối ưu hóa trình biên dịch (còn gọi là cú pháp đường?). Một số người đã gọi nó là Đối tượng của Người nghèo .

Xem câu trả lời của Eric Lippert : (trích đoạn dưới đây)

Trình biên dịch sẽ tạo mã như thế này:

private class Locals
{
  public int count;
  public void Anonymous()
  {
    this.count++;
  }
}

public Action Counter()
{
  Locals locals = new Locals();
  locals.count = 0;
  Action counter = new Action(locals.Anonymous);
  return counter;
}

Có lý?
Ngoài ra, bạn yêu cầu so sánh. VB và JScript đều tạo ra các bao đóng theo cách khá giống nhau.


Câu trả lời này là CW vì tôi không xứng đáng nhận được điểm cho câu trả lời tuyệt vời của Eric. Vui lòng upvote nó khi bạn thấy phù hợp. HTH
goodguys_activate

3
-1: Giải thích của bạn quá gốc trong C #. Đóng cửa được sử dụng trong nhiều ngôn ngữ và nhiều hơn so với đường cú pháp trong các ngôn ngữ này và bao gồm cả chức năng và trạng thái.
Martin York

1
Không, đóng cửa không chỉ là "tối ưu hóa trình biên dịch" hay đường cú pháp. -1
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.