การปิดคืออะไร


155

ทุกครั้งที่ฉันเห็น "การปิด" ถูกกล่าวถึงและฉันพยายามค้นหา แต่ Wiki ไม่ได้ให้คำอธิบายที่ฉันเข้าใจ มีคนช่วยฉันที่นี่ได้ไหม


หากคุณรู้ว่า Java / C # หวังว่าลิงก์นี้จะช่วยให้ - http://www.developerfusion.com/article/8251/the-beauty-of-closures/
Gulshan

1
การปิดยากที่จะเข้าใจ คุณควรลองคลิกที่ลิงก์ทั้งหมดในประโยคแรกของบทความ Wikipedia และทำความเข้าใจกับบทความเหล่านั้นก่อน
Zach


3
อะไรคือความแตกต่างพื้นฐานระหว่างการปิดและการเรียน? โอเคชั้นเรียนที่มีวิธีสาธารณะเพียงวิธีเดียว
biziclop

5
@biziclop: คุณสามารถเลียนแบบการปิดคลาส (นั่นคือสิ่งที่ Java devs ต้องทำ) แต่โดยปกติแล้วพวกเขาจะสร้างน้อยกว่าเล็กน้อยและคุณไม่ต้องจัดการด้วยตนเองในสิ่งที่คุณกำลังทำอยู่ (เสียงกระเพื่อมไม่ยอมใครง่ายๆถามคำถามที่คล้ายกัน แต่มาถึงข้อสรุปอื่น ๆ - การสนับสนุน OO ระดับภาษานั้นไม่จำเป็นเมื่อคุณปิด)

คำตอบ:


141

(ข้อจำกัดความรับผิดชอบ: นี่เป็นคำอธิบายพื้นฐานเท่าที่คำจำกัดความมีความหมายฉันจะลดความซับซ้อนลงเล็กน้อย)

วิธีที่ง่ายที่สุดในการนึกถึงการปิดคือฟังก์ชั่นที่สามารถจัดเก็บเป็นตัวแปร (เรียกว่า "ฟังก์ชั่นชั้นหนึ่ง") ที่มีความสามารถพิเศษในการเข้าถึงตัวแปรอื่น ๆ ภายในขอบเขตที่มันถูกสร้างขึ้น

ตัวอย่าง (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();

ฟังก์ชั่น1ได้รับมอบหมายให้document.onclickและdisplayValOfBlackกำลังปิด คุณสามารถเห็นว่าพวกเขาทั้งสองอ้างอิงตัวแปรบูลีนblackแต่ตัวแปรนั้นได้รับมอบหมายนอกฟังก์ชั่น เนื่องจากblackอยู่ภายในขอบเขตที่ฟังก์ชันถูกกำหนดตัวชี้ไปยังตัวแปรนี้จึงถูกสงวนไว้

หากคุณใส่สิ่งนี้ลงในหน้า HTML:

  1. คลิกเพื่อเปลี่ยนเป็นสีดำ
  2. กดปุ่ม [Enter] เพื่อดู "จริง"
  3. คลิกอีกครั้งเปลี่ยนกลับเป็นสีขาว
  4. กดปุ่ม [Enter] เพื่อดู "false"

นี่แสดงให้เห็นว่าทั้งสองมีการเข้าถึงเหมือนกัน blackและสามารถใช้ในการจัดเก็บสถานะโดยไม่มีวัตถุห่อหุ้มใด ๆ

การเรียกใช้setKeyPressเพื่อแสดงให้เห็นว่าสามารถส่งผ่านฟังก์ชันเหมือนกับตัวแปรใด ๆ ได้อย่างไร ขอบเขตเก็บรักษาไว้ในการปิดยังคงเป็นหนึ่งที่ฟังก์ชั่นที่ถูกกำหนดไว้

ปกติจะใช้การปิดเป็นตัวจัดการเหตุการณ์โดยเฉพาะใน JavaScript และ ActionScript การใช้งานการปิดที่ดีจะช่วยให้คุณผูกตัวแปรกับตัวจัดการเหตุการณ์โดยไม่ต้องสร้าง wrapper วัตถุ อย่างไรก็ตามการใช้อย่างไม่ระมัดระวังจะนำไปสู่การรั่วไหลของหน่วยความจำ (เช่นเมื่อตัวจัดการเหตุการณ์ที่ไม่ได้ใช้ แต่เก็บรักษาไว้เป็นสิ่งเดียวที่จะเก็บวัตถุขนาดใหญ่ไว้ในหน่วยความจำโดยเฉพาะอย่างยิ่งวัตถุ DOM ป้องกันการรวบรวมขยะ)


1: ที่จริงแล้วฟังก์ชั่นทั้งหมดใน JavaScript ปิดลง


3
ขณะที่ฉันอ่านคำตอบของคุณฉันรู้สึกว่ามีหลอดไฟเปิดอยู่ในใจของฉัน ชื่นชมมาก! :)
Jay

1
เนื่องจากblackมีการประกาศภายในฟังก์ชันจะไม่ถูกทำลายเมื่อกองซ้อนคลาย ... ?
gablin

1
@gablin นั่นคือสิ่งที่เป็นเอกลักษณ์เกี่ยวกับภาษาที่มีการปิด ภาษาทั้งหมดที่มีการรวบรวมขยะทำงานในลักษณะเดียวกันมาก - เมื่อไม่มีการอ้างอิงอีกต่อไปที่วัตถุมันสามารถถูกทำลายได้ เมื่อใดก็ตามที่ฟังก์ชั่นถูกสร้างขึ้นใน JS ขอบเขตท้องถิ่นจะถูกผูกไว้กับฟังก์ชั่นนั้นจนกว่าฟังก์ชั่นนั้นจะถูกทำลาย
Nicole

2
@ แกบลินนั่นเป็นคำถามที่ดี ฉันไม่คิดว่าพวกเขาไม่สามารถ & mdash; แต่ฉันนำคอลเลคชั่นขยะขึ้นมาเท่านั้นเพราะสิ่งที่ JS ใช้และนั่นคือสิ่งที่คุณดูเหมือนจะอ้างถึงเมื่อคุณพูดว่า "ตั้งแต่blackมีการประกาศภายในฟังก์ชันจะไม่ถูกทำลาย" โปรดจำไว้ว่าถ้าคุณประกาศวัตถุในฟังก์ชั่นแล้วกำหนดให้กับตัวแปรที่อาศัยอยู่ที่อื่นวัตถุนั้นจะถูกเก็บรักษาไว้เพราะมีการอ้างอิงอื่น ๆ
Nicole

1
Objective-C (และ C ภายใต้เสียงดังกราว) รองรับบล็อกซึ่งเป็นหลักปิดโดยไม่ต้องเก็บขยะ มันต้องการการสนับสนุนรันไทม์และการแทรกแซงด้วยตนเองบางอย่างเกี่ยวกับการจัดการหน่วยความจำ
Quixoto

68

การปิดเป็นวิธีที่แตกต่างกันในการมองวัตถุ วัตถุคือข้อมูลที่มีอย่างน้อยหนึ่งฟังก์ชั่นผูกอยู่กับมัน การปิดเป็นฟังก์ชั่นที่มีตัวแปรอย่างน้อยหนึ่งตัวผูกอยู่กับมัน ทั้งสองมีความเหมือนกันโดยทั่วไปในระดับการดำเนินการอย่างน้อย ความแตกต่างที่แท้จริงคือสิ่งที่พวกเขามาจาก

ในการเขียนโปรแกรมเชิงวัตถุคุณประกาศคลาสอ็อบเจ็กต์โดยกำหนดตัวแปรสมาชิกและวิธีการ (ฟังก์ชันสมาชิก) ล่วงหน้าแล้วคุณสร้างอินสแตนซ์ของคลาสนั้น แต่ละอินสแตนซ์มาพร้อมกับสำเนาของข้อมูลสมาชิกซึ่งเริ่มต้นโดยตัวสร้าง จากนั้นคุณจะมีตัวแปรชนิดของวัตถุและส่งผ่านเป็นข้อมูลเพราะโฟกัสนั้นเป็นลักษณะของข้อมูล

ในทางกลับกันวัตถุไม่ได้ถูกกำหนดไว้ล่วงหน้าเช่นคลาสอ็อบเจ็กต์หรืออินสแตนซ์ผ่านการเรียกคอนสตรัคเตอร์ในรหัสของคุณ แต่คุณเขียนการปิดเป็นฟังก์ชันภายในของฟังก์ชันอื่น การปิดสามารถอ้างถึงตัวแปรท้องถิ่นของฟังก์ชั่นด้านนอกและคอมไพเลอร์ตรวจจับและย้ายตัวแปรเหล่านี้จากพื้นที่สแต็คของฟังก์ชั่นด้านนอกไปยังการประกาศวัตถุที่ซ่อนอยู่ของการปิด จากนั้นคุณมีตัวแปรประเภทการปิดและแม้ว่าโดยทั่วไปแล้วมันเป็นวัตถุภายใต้ประทุนคุณจะส่งมันไปรอบ ๆ เพื่ออ้างอิงการทำงานเนื่องจากโฟกัสอยู่ที่ธรรมชาติของมันในฐานะฟังก์ชัน


3
+1: คำตอบที่ดี คุณสามารถเห็นการปิดเป็นวัตถุด้วยวิธีการเดียวเท่านั้นและวัตถุที่กำหนดเองเป็นการรวบรวมการปิดทับข้อมูลพื้นฐานบางอย่าง (ตัวแปรสมาชิกของวัตถุ) ฉันคิดว่าทั้งสองมุมมองนั้นค่อนข้างสมมาตร
Giorgio

3
คำตอบที่ดีมาก จริง ๆ แล้วมันอธิบายความเข้าใจด้านการปิด
RoboAlex

1
@Mason Wheeler: ข้อมูลการปิดถูกเก็บไว้ที่ไหน ในกองเหมือนฟังก์ชั่นหรือไม่? หรือในกองเหมือนวัตถุ?
RoboAlex

1
@RoboAlex: ในกองเพราะมันเป็นวัตถุที่ดูเหมือนฟังก์ชั่น
Mason Wheeler

1
@RoboAlex: ตำแหน่งที่ปิดและเก็บข้อมูลที่บันทึกไว้นั้นขึ้นอยู่กับการใช้งาน ใน C ++ สามารถเก็บไว้ใน heap หรือใน stack
Giorgio

29

การปิดเทอมมาจากข้อเท็จจริงที่ว่าส่วนหนึ่งของรหัส (บล็อกฟังก์ชัน) สามารถมีตัวแปรอิสระที่ถูกปิด (เช่นถูกผูกไว้กับค่า) โดยสภาพแวดล้อมที่มีการกำหนดบล็อกของรหัส

ยกตัวอย่างนิยามฟังก์ชั่น Scala:

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

ในส่วนของฟังก์ชั่นมีสองชื่อ (ตัวแปร) vและkระบุค่าจำนวนเต็มสองค่า ชื่อvถูกผูกไว้เพราะมันถูกประกาศเป็นอาร์กิวเมนต์ของฟังก์ชั่นaddConstant(โดยดูที่การประกาศฟังก์ชั่นที่เรารู้ว่าvจะได้รับการกำหนดค่าเมื่อมีการเรียกใช้ฟังก์ชั่น) ชื่อkเป็นอิสระ wrt ฟังก์ชั่นaddConstantเพราะฟังก์ชั่นที่มีเงื่อนงำไม่เป็นสิ่งที่ค่าkถูกผูกไว้กับ (และวิธี)

เพื่อประเมินการโทรเช่น:

val n = addConstant(10)

เราต้องกำหนดkค่าซึ่งสามารถเกิดขึ้นได้หากชื่อkถูกกำหนดในบริบทที่addConstantกำหนดไว้เท่านั้น ตัวอย่างเช่น:

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

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

  values.map(addConstant)
}

ตอนนี้เราได้นิยามไว้addConstantในบริบทที่kถูกกำหนดแล้วaddConstantกลายเป็นการปิดเพราะตัวแปรอิสระทั้งหมดตอนนี้ปิด (ผูกกับค่า): addConstantสามารถเรียกใช้และส่งผ่านได้ราวกับว่ามันเป็นฟังก์ชั่น หมายเหตุตัวแปรอิสระที่kถูกผูกไว้กับค่าเมื่อปิดจะถูกกำหนดไว้ในขณะที่ตัวแปรอาร์กิวเมนต์vที่ถูกผูกไว้เมื่อปิดจะเรียก

ดังนั้นการปิดจึงเป็นฟังก์ชั่นหรือบล็อคโค้ดที่สามารถเข้าถึงค่าที่ไม่ใช่ของท้องถิ่นผ่านตัวแปรอิสระหลังจากสิ่งเหล่านี้ถูกผูกไว้กับบริบท

ในหลายภาษาถ้าคุณใช้การปิดเพียงครั้งเดียวคุณสามารถทำให้มันไม่ระบุชื่อเช่น

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

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

โปรดทราบว่าฟังก์ชั่นที่ไม่มีตัวแปรอิสระเป็นกรณีพิเศษของการปิด (พร้อมชุดว่างของตัวแปรอิสระ) Analogously เป็นฟังก์ชั่นที่ไม่ระบุชื่อเป็นกรณีพิเศษของการปิดไม่ระบุชื่อคือฟังก์ชั่นที่ไม่ระบุชื่อเป็นปิดที่ไม่ระบุชื่อกับไม่มีตัวแปรฟรี


jibes นี้ดีกับสูตรปิดและเปิดในตรรกะ ขอบคุณสำหรับคำตอบ.
RainDoctor

@RainDoctor: ตัวแปรอิสระถูกกำหนดไว้ในสูตรตรรกะและในการแสดงออกแคลคูลัสแลมบ์ดาในลักษณะที่คล้ายกัน: แลมบ์ดาในการแสดงออกแลมบ์ดาทำงานได้เหมือนตัวหาปริมาณในสูตรตรรกะ WRT ตัวแปรอิสระ / ขอบเขต
Giorgio

9

คำอธิบายง่ายๆใน 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)closureจะใช้ค่าที่สร้างขึ้นก่อนหน้านี้ alertValueเนมสเปซของฟังก์ชันที่ส่งคืนจะเชื่อมต่อกับเนมสเปซที่มีclosureตัวแปรอยู่ เมื่อคุณลบฟังก์ชั่นทั้งหมดค่าของclosureตัวแปรจะถูกลบ แต่จนกว่าจะถึงเวลานั้นalertValueฟังก์ชันจะสามารถอ่าน / เขียนค่าของตัวแปรclosureได้เสมอ

หากคุณเรียกใช้รหัสนี้การวนซ้ำครั้งแรกจะกำหนดค่า 0 ให้กับclosureตัวแปรและเขียนฟังก์ชันใหม่เพื่อ:

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

และเนื่องจากalertValueความต้องการตัวแปรท้องถิ่นที่จะดำเนินการฟังก์ชั่นที่มันผูกตัวเองด้วยค่าของตัวแปรในท้องถิ่นที่ได้รับมอบหมายก่อนหน้านี้closureclosure

และตอนนี้ทุกครั้งที่คุณเรียกใช้closure_exampleฟังก์ชันมันจะเขียนค่าที่เพิ่มขึ้นของclosureตัวแปรเพราะalert(closure)ถูกผูกไว้

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

ขอบคุณฉันไม่ได้ทดสอบ code =) ทุกอย่างดูโอเคแล้ว
Muha

5

"การปิด" คือสิ่งสำคัญรัฐท้องถิ่นและรหัสบางอย่างรวมกันเป็นแพคเกจ โดยทั่วไปแล้วสถานะโลคัลมาจากขอบเขตโดยรอบ (คำศัพท์) และโค้ดคือ (โดยพื้นฐาน) ฟังก์ชั่นด้านในซึ่งจะส่งกลับไปด้านนอก การปิดคือการรวมกันของตัวแปรที่จับที่ฟังก์ชั่นภายในเห็นและรหัสของฟังก์ชั่นภายใน

เป็นหนึ่งในสิ่งที่น่าเสียดายที่อธิบายได้ยากเนื่องจากไม่คุ้นเคย

สิ่งหนึ่งที่ฉันใช้ประสบความสำเร็จในอดีตคือ "จินตนาการว่าเรามีบางสิ่งที่เราเรียกว่า 'หนังสือ' ในการปิดห้อง 'หนังสือ' คือสำเนานั่นตรงนั้นมุม TAOCP แต่อยู่บนโต๊ะ มันคือสำเนาของหนังสือของเดรสเดนไฟล์ดังนั้นขึ้นอยู่กับว่าคุณปิดหนังสืออะไร


คุณลืมสิ่งนี้: en.wikipedia.org/wiki/Closure_(computer_programming)ในคำตอบของคุณ
S.Lott

3
ไม่ฉันเลือกที่จะไม่ปิดหน้านั้นอย่างตั้งใจ
Vatine

"สถานะและฟังก์ชั่น": ฟังก์ชั่น C ที่มีstaticตัวแปรโลคัลสามารถถูกพิจารณาว่าเป็นการปิดหรือไม่? การปิดใน Haskell เกี่ยวข้องกับรัฐหรือไม่
Giorgio

2
@Giorgio Closures in Haskell ทำ (ฉันเชื่อว่า) ปิดข้อโต้แย้งในขอบเขตศัพท์ที่พวกเขากำหนดไว้ดังนั้นฉันจะพูดว่า "ใช่" (แม้ว่าฉันจะไม่คุ้นเคยกับ Haskell) ฟังก์ชั่น AC ที่มีตัวแปรแบบคงที่คือการปิดที่ จำกัด ที่สุด (คุณต้องการให้สามารถสร้างการปิดหลายครั้งจากฟังก์ชั่นเดียวโดยใช้staticตัวแปรเฉพาะที่คุณมีอย่างเดียว)
Vatine

ฉันถามคำถามนี้โดยมีจุดประสงค์เพราะฉันคิดว่าฟังก์ชั่น C ที่มีตัวแปรแบบสแตติกไม่ใช่การปิด: ตัวแปรสแตติกถูกกำหนดไว้ในเครื่องและรู้จักกันเฉพาะในการปิดเท่านั้นไม่สามารถเข้าถึงสภาพแวดล้อมได้ นอกจากนี้ฉันไม่แน่ใจ 100% แต่ฉันจะกำหนดคำสั่งของคุณในทางกลับกัน: คุณใช้กลไกการปิดเพื่อสร้างฟังก์ชั่นที่แตกต่างกัน (ฟังก์ชั่นคือคำจำกัดความการปิด + การผูกกับตัวแปรอิสระ)
จอร์โจ

5

เป็นการยากที่จะกำหนดว่าการปิดใดโดยไม่กำหนดแนวคิดของ 'สถานะ'

โดยทั่วไปในภาษาที่มีการกำหนดขอบเขตคำศัพท์เต็มรูปแบบที่ปฏิบัติหน้าที่เป็นค่านิยมชั้นหนึ่งมีสิ่งพิเศษเกิดขึ้น ถ้าฉันจะทำสิ่งที่ชอบ:

function foo(x)
return x
end

x = foo

ตัวแปรxไม่เพียงfunction foo()แต่อ้างอิงแต่ยังอ้างอิงสถานะที่fooถูกทิ้งไว้ในครั้งสุดท้ายที่มันกลับมา เวทมนตร์ที่แท้จริงเกิดขึ้นเมื่อfooมีฟังก์ชั่นอื่น ๆ ที่กำหนดไว้เพิ่มเติมภายในขอบเขตของมัน; มันเหมือนกับสภาพแวดล้อมขนาดเล็กของตัวเอง (เช่นเดียวกับ 'ปกติ' เรากำหนดฟังก์ชั่นในสภาพแวดล้อมระดับโลก)

ในทางปฏิบัติมันสามารถแก้ปัญหาต่าง ๆ มากมายเช่นเดียวกับคำสำคัญ 'แบบคงที่' ของ C ++ (C?) ซึ่งยังคงสถานะของตัวแปรท้องถิ่นตลอดการเรียกฟังก์ชั่นหลาย ๆ แต่มันก็เหมือนกับการใช้หลักการเดียวกันนั้น (ตัวแปรคงที่) กับฟังก์ชั่นเนื่องจากฟังก์ชั่นเป็นค่าที่ดีที่สุด การปิดเพิ่มการสนับสนุนสำหรับสถานะทั้งหมดของฟังก์ชั่นที่จะบันทึก (ไม่มีส่วนเกี่ยวข้องกับฟังก์ชั่นคงที่ของ C ++)

การรักษาฟังก์ชั่นเป็นค่าชั้นหนึ่งและเพิ่มการสนับสนุนสำหรับการปิดก็หมายความว่าคุณสามารถมีฟังก์ชั่นเดียวกันมากกว่าหนึ่งอินสแตนซ์ในหน่วยความจำ (คล้ายกับคลาส) สิ่งนี้หมายความว่าคุณสามารถใช้รหัสเดิมซ้ำได้โดยไม่ต้องรีเซ็ตสถานะของฟังก์ชันตามที่ต้องการเมื่อจัดการกับตัวแปรสแตติก C ++ ภายในฟังก์ชัน (อาจผิดเกี่ยวกับเรื่องนี้?)

นี่คือการทดสอบการสนับสนุนการปิดของ 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

ผล:

nil
20
31
42

มันอาจจะยุ่งยากและอาจแตกต่างกันไปในแต่ละภาษา แต่ดูเหมือนว่า Lua ทุกครั้งที่มีการใช้งานฟังก์ชั่นสถานะของมันก็จะถูกรีเซ็ต ฉันพูดแบบนี้เพราะผลลัพธ์จากรหัสด้านบนจะแตกต่างกันถ้าเราเข้าถึงmyclosureฟังก์ชั่น / สถานะโดยตรง (แทนที่จะผ่านฟังก์ชั่นที่ไม่ระบุชื่อมันจะส่งคืน) ตามที่pvalueจะถูกรีเซ็ตกลับเป็น 10; แต่ถ้าเราเข้าถึงสถานะของการรู้แจ้งผ่าน x (ฟังก์ชั่นนิรนาม) คุณจะเห็นว่าpvalueมันยังมีชีวิตอยู่และอยู่ในความทรงจำ ฉันสงสัยว่ามีอะไรเพิ่มเติมอีกเล็กน้อยบางทีบางคนสามารถอธิบายลักษณะของการปฏิบัติได้ดีกว่า

PS: ฉันไม่รู้จัก C ++ 11 (นอกเหนือจากรุ่นก่อนหน้า) ดังนั้นโปรดทราบว่านี่ไม่ใช่การเปรียบเทียบระหว่างการปิดใน C ++ 11 และ Lua นอกจากนี้ 'เส้นที่ลาก' ทั้งหมดจาก Lua ถึง C ++ มีความคล้ายคลึงกันเนื่องจากตัวแปรแบบสแตติกและการปิดไม่เหมือนกัน 100% แม้ว่าบางครั้งพวกเขาจะใช้เพื่อแก้ปัญหาที่คล้ายกัน

สิ่งที่ฉันไม่แน่ใจก็คือในตัวอย่างโค้ดข้างต้นไม่ว่าจะเป็นฟังก์ชั่นที่ไม่ระบุชื่อหรือฟังก์ชั่นการสั่งซื้อที่สูงกว่าถือว่าเป็นการปิดหรือไม่?


4

การปิดเป็นฟังก์ชันที่มีสถานะเชื่อมโยง:

ใน Perl คุณสร้างการปิดเช่นนี้:

#!/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

ถ้าเราดูการทำงานใหม่ที่มาพร้อมกับ C ++
นอกจากนี้ยังช่วยให้คุณผูกสถานะปัจจุบันกับวัตถุ:

#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

ลองพิจารณาฟังก์ชั่นง่าย ๆ :

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

ฟังก์ชั่นนี้เรียกว่าฟังก์ชั่นระดับบนสุดเพราะมันไม่ซ้อนอยู่ภายในฟังก์ชั่นอื่น ๆ ทุกคนร่วมงานฟังก์ชั่นจาวาสคริปต์ด้วยตัวเองรายชื่อของวัตถุที่เรียกว่า"ขอบเขตในเครือ" ขอบเขตขอบเขตนี้เป็นรายการของวัตถุที่เรียงลำดับแล้ว แต่ละวัตถุเหล่านี้กำหนดตัวแปรบางอย่าง

ในฟังก์ชั่นระดับบนสุดห่วงโซ่ขอบเขตประกอบด้วยวัตถุเดียววัตถุทั่วโลก ตัวอย่างเช่นฟังก์ชั่นf1ด้านบนมีห่วงโซ่ขอบเขตที่มีวัตถุเดียวในนั้นที่กำหนดตัวแปรทั่วโลกทั้งหมด (โปรดทราบว่าคำว่า "วัตถุ" ที่นี่ไม่ได้หมายถึงวัตถุ JavaScript มันเป็นเพียงวัตถุที่กำหนดการใช้งานที่ทำหน้าที่เป็นตัวแปรคอนเทนเนอร์ซึ่ง JavaScript สามารถ "ค้นหา" ตัวแปร)

เมื่อมีการเรียกใช้ฟังก์ชันนี้ JavaScript จะสร้างสิ่งที่เรียกว่า"วัตถุการเปิดใช้งาน"และวางไว้ที่ด้านบนของห่วงโซ่ขอบเขต วัตถุนี้มีตัวแปรท้องถิ่นทั้งหมด (เช่นxที่นี่) ดังนั้นตอนนี้เรามีวัตถุสองรายการในห่วงโซ่ขอบเขต: สิ่งแรกคือวัตถุการเปิดใช้งานและด้านล่างเป็นวัตถุทั่วโลก

โปรดระมัดระวังอย่างยิ่งว่าวัตถุทั้งสองจะถูกใส่เข้าไปในห่วงโซ่ขอบเขตในเวลาที่ต่างกัน วัตถุทั่วโลกจะถูกวางเมื่อฟังก์ชั่นที่ถูกกำหนด (เช่นเมื่อ JavaScript แยกวิเคราะห์ฟังก์ชั่นและสร้างวัตถุฟังก์ชั่น) และวัตถุการเปิดใช้งานเข้าสู่เมื่อมีการเรียกใช้ฟังก์ชั่น

ดังนั้นตอนนี้เรารู้สิ่งนี้:

  • ฟังก์ชั่นทุกคนมีห่วงโซ่ขอบเขตที่เกี่ยวข้อง
  • เมื่อฟังก์ชั่นถูกกำหนด (เมื่อสร้างฟังก์ชั่นวัตถุ) JavaScript จะบันทึกห่วงโซ่ขอบเขตด้วยฟังก์ชั่นนั้น
  • สำหรับฟังก์ชั่นระดับสูงสุดห่วงโซ่ขอบเขตมีเพียงวัตถุส่วนกลางที่เวลานิยามฟังก์ชั่นและเพิ่มวัตถุการเปิดใช้งานเพิ่มเติมด้านบนในเวลาที่เรียก

สถานการณ์น่าสนใจเมื่อเราจัดการกับฟังก์ชั่นที่ซ้อนกัน ดังนั้นเรามาสร้างหนึ่ง:

function f1(x) {

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

}

เมื่อf1ได้รับการกำหนดเราจะได้รับห่วงโซ่ขอบเขตสำหรับมันมีเพียงวัตถุทั่วโลก

ตอนนี้เมื่อ f1ถูกเรียกใช้ขอบเขตของการf1รับวัตถุการเปิดใช้งาน วัตถุการเปิดใช้งานนี้มีตัวแปรxและตัวแปรf2ซึ่งเป็นฟังก์ชั่น และทราบว่าf2ได้รับการกำหนด ดังนั้นที่จุดนี้, f2จาวาสคริปต์ยังช่วยประหยัดโซ่ขอบเขตใหม่สำหรับ ห่วงโซ่ขอบเขตที่บันทึกไว้สำหรับฟังก์ชั่นภายในนี้เป็นห่วงโซ่ขอบเขตปัจจุบันที่มีผล ห่วงโซ่ขอบเขตในปัจจุบันมีผลเป็นที่ของf1's ดังนั้นf2'ห่วงโซ่ขอบเขตเป็นf1' s ปัจจุบันห่วงโซ่ขอบเขต - ซึ่งมีการเปิดใช้งานวัตถุf1และวัตถุทั่วโลก

เมื่อf2ถูกเรียกมันจะได้รับเป็นวัตถุเปิดใช้งานของตัวเองที่มีyเพิ่มลงในห่วงโซ่ขอบเขตซึ่งมีวัตถุเปิดใช้งานf1และวัตถุทั่วโลกแล้ว

หากมีฟังก์ชันซ้อนกันอีกหนึ่งรายการที่กำหนดไว้ภายในf2ขอบเขตของห่วงโซ่นั้นจะมีวัตถุสามรายการในเวลาที่กำหนด (2 วัตถุการเปิดใช้งานของสองฟังก์ชันภายนอกและวัตถุทั่วโลก) และ 4 ในเวลาที่เรียกใช้

ดังนั้นตอนนี้เราเข้าใจวิธีการทำงานของขอบเขตการทำงาน แต่เรายังไม่ได้พูดคุยเกี่ยวกับการปิด

การรวมกันของฟังก์ชั่นวัตถุและขอบเขต (ชุดของการผูกตัวแปร) ซึ่งตัวแปรของฟังก์ชั่นได้รับการแก้ไขเรียกว่าการปิดในวรรณคดีวิทยาศาสตร์คอมพิวเตอร์ - JavaScript คำแนะนำที่ชัดเจนโดย David Flanagan

ฟังก์ชั่นส่วนใหญ่ถูกเรียกใช้โดยใช้ขอบเขตลูกโซ่เดียวกันที่มีผลเมื่อฟังก์ชั่นถูกกำหนดและมันไม่สำคัญว่าจะมีการปิดที่เกี่ยวข้อง การปิดเป็นเรื่องที่น่าสนใจเมื่อมีการเรียกใช้ภายใต้ขอบเขตของขอบเขตที่แตกต่างจากที่ระบุไว้ สิ่งนี้เกิดขึ้นได้บ่อยที่สุดเมื่อวัตถุฟังก์ชันที่ซ้อนกันถูกส่งคืนจากฟังก์ชันที่มันถูกกำหนดไว้

เมื่อฟังก์ชันส่งคืนวัตถุการเปิดใช้งานนั้นจะถูกลบออกจากห่วงโซ่ขอบเขต หากไม่มีฟังก์ชั่นซ้อนกันจะไม่มีการอ้างอิงไปยังวัตถุการเปิดใช้งานอีกต่อไปและจะได้รับการรวบรวมขยะ หากมีฟังก์ชั่นที่ซ้อนกันที่กำหนดไว้แล้วแต่ละฟังก์ชั่นเหล่านั้นมีการอ้างอิงถึงห่วงโซ่ขอบเขตและห่วงโซ่ขอบเขตนั้นหมายถึงวัตถุเปิดใช้งาน

หากวัตถุที่ซ้อนกันเหล่านั้นยังคงอยู่ในฟังก์ชั่นด้านนอกของพวกเขาแล้วพวกเขาเองจะถูกเก็บรวบรวมขยะพร้อมกับวัตถุการเปิดใช้งานที่พวกเขาอ้างถึง แต่ถ้าฟังก์ชั่นกำหนดฟังก์ชั่นที่ซ้อนกันและส่งกลับหรือเก็บไว้ในสถานที่ให้บริการแล้วจะมีการอ้างอิงภายนอกไปยังฟังก์ชั่นที่ซ้อนกัน มันจะไม่ถูกรวบรวมขยะและวัตถุการเปิดใช้งานที่อ้างถึงจะไม่ถูกเก็บขยะด้วยเช่นกัน

ในตัวอย่างข้างต้นของเราเราไม่ได้กลับf2จากf1ดังนั้นเมื่อมีการเรียกร้องให้f1ผลตอบแทนวัตถุการเปิดใช้งานจะถูกลบออกจากห่วงโซ่ขอบเขตและเก็บขยะ แต่ถ้าเรามีบางอย่างเช่นนี้:

function f1(x) {

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

    return f2;
}

ที่นี่การส่งคืนf2จะมีห่วงโซ่ขอบเขตที่จะมีวัตถุการเปิดใช้งานf1และด้วยเหตุนี้มันจะไม่ถูกรวบรวมขยะ ณ จุดนี้ถ้าเราเรียกf2มันจะสามารถเข้าถึงf1ตัวแปรของxเราได้แม้ว่าเราจะไม่อยู่f1ก็ตาม

ดังนั้นเราจะเห็นได้ว่าฟังก์ชั่นเก็บรักษาขอบเขตของห่วงโซ่ไว้ด้วยและด้วยห่วงโซ่ขอบเขตจะมีวัตถุการเปิดใช้งานทั้งหมดของฟังก์ชั่นด้านนอก นี่คือสาระสำคัญของการปิด เราบอกว่าฟังก์ชั่นในจาวาสคริปต์เป็น"ขอบเขตขอบเขต"ซึ่งหมายความว่าพวกเขาบันทึกขอบเขตที่แอ็คทีฟเมื่อถูกกำหนดเมื่อเทียบกับขอบเขตที่แอ็คทีฟเมื่อถูกเรียก

มีเทคนิคการเขียนโปรแกรมที่ทรงพลังมากมายที่เกี่ยวข้องกับการปิดเช่นการประมาณตัวแปรส่วนตัวการเขียนโปรแกรมที่ขับเคลื่อนด้วยเหตุการณ์การใช้งานบางส่วนเป็นต้น

นอกจากนี้โปรดทราบว่าทั้งหมดนี้ใช้กับภาษาทั้งหมดที่สนับสนุนการปิด ตัวอย่างเช่น PHP (5.3+), Python, Ruby และอื่น ๆ


-1

การปิดคือการเพิ่มประสิทธิภาพคอมไพเลอร์ (หรือที่รู้จักว่าน้ำตาล syntactic?) บางคนเรียกสิ่งนี้ว่าเป็นวัตถุของชายผู้น่าสงสารเช่นกัน

ดูคำตอบของ Eric Lippert : (ตัดตอนมาด้านล่าง)

คอมไพเลอร์จะสร้างรหัสดังนี้:

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

ทำให้รู้สึก?
นอกจากนี้คุณขอเปรียบเทียบ VB และ JScript ทั้งคู่สร้างการปิดในลักษณะเดียวกัน


คำตอบนี้เป็น CW เพราะฉันไม่สมควรได้รับคะแนนสำหรับคำตอบที่ยอดเยี่ยมของ Eric โปรดโหวตมันตามที่เห็นสมควร HTH
goodguys_activate

3
-1: คำอธิบายของคุณหยั่งรากเกินไปใน C # การปิดการใช้งานในหลายภาษาและมากกว่าการสร้างประโยคน้ำตาลในภาษาเหล่านี้และรวมทั้งฟังก์ชั่นและสถานะ
Martin York

1
ไม่การปิดไม่ได้เป็นเพียงแค่ "การเพิ่มประสิทธิภาพคอมไพเลอร์" หรือน้ำตาลเชิงซ้อน -1
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.