'ปิด' คืออะไร?


432

ฉันถามคำถามเกี่ยวกับการปิดบัญชีและการปิดบัญชีถูกกล่าวถึง การปิดคืออะไร มันเกี่ยวข้องกับแกงอย่างไร?


22
ตอนนี้สิ่งที่ปิดคืออะไร ??? บางคำตอบบอกว่าการปิดเป็นฟังก์ชั่น บ้างก็ว่าเป็นกองซ้อน บางคำตอบบอกว่ามันคือค่า "ซ่อน" เพื่อความเข้าใจของฉันมันเป็นฟังก์ชั่น + ตัวแปรล้อมรอบ
โรลันด์

3
อธิบายว่าการปิดคืออะไร: stackoverflow.com/questions/4103750/…
dietbuddha

นอกจากนี้ยังมีการดูการปิดคืออะไร? ที่ softwareengineering.stackexchange
B12Toaster

อธิบายการปิดและการใช้งานทั่วไปคืออะไร: trungk18.com/experience/javascript-closure
Sasuke91

คำตอบ:


743

ขอบเขตตัวแปร

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

function() {
  var a = 1;
  console.log(a); // works
}    
console.log(a); // fails

หากฉันพยายามเข้าถึงตัวแปรท้องถิ่นภาษาส่วนใหญ่จะมองหามันในขอบเขตปัจจุบันจากนั้นเลื่อนดูขอบเขตหลักจนกว่าจะถึงขอบเขตรูท

var a = 1;
function() {
  console.log(a); // works
}    
console.log(a); // works

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

นี่คือวิธีที่เราคาดหวังสิ่งต่าง ๆ ในการทำงานตามปกติ

การปิดคือขอบเขตตัวแปรเฉพาะที่แบบต่อเนื่อง

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

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

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

ตัวอย่างเช่น

นี่เป็นตัวอย่างง่ายๆใน JavaScript ที่แสดงจุด:

outer = function() {
  var a = 1;
  var inner = function() {
    console.log(a);
  }
  return inner; // this returns a function
}

var fnc = outer(); // execute outer to get inner 
fnc();

ที่นี่ฉันได้กำหนดฟังก์ชั่นภายในฟังก์ชั่น ภายใน gains aเข้าถึงฟังก์ชั่นตัวแปรท้องถิ่นทุกฟังก์ชั่นด้านนอกรวมทั้ง ตัวแปรaอยู่ในขอบเขตของฟังก์ชันภายใน

โดยปกติเมื่อออกจากฟังก์ชั่นตัวแปรท้องถิ่นทั้งหมดของมันจะปลิวไป แต่ถ้าเรากลับมาฟังก์ชั่นด้านในและกำหนดให้ตัวแปรfncเพื่อที่จะยังคงอยู่หลังจากouterได้ออก, ทั้งหมดของตัวแปรที่อยู่ในขอบเขตเมื่อinnerถูกกำหนดยังคงมีอยู่ ตัวแปรaถูกปิดไป - มันอยู่ในช่วงปิด

โปรดทราบว่าตัวแปรทั้งหมดส่วนตัวถึงa fncนี่เป็นวิธีการสร้างตัวแปรส่วนตัวในภาษาที่ใช้งานได้เช่นจาวาสคริปต์

อย่างที่คุณอาจเดาได้เมื่อฉันเรียกfnc()มันว่าพิมพ์ค่าของaซึ่งก็คือ "1"

ในภาษาที่ไม่มีการปิดตัวแปรaจะมีการรวบรวมขยะและถูกโยนทิ้งไปเมื่อฟังก์ชั่นouterออก การโทร fnc อาจเกิดข้อผิดพลาดเนื่องจากaไม่มีอยู่อีกต่อไป

ใน JavaScript ตัวแปรaยังคงมีอยู่เนื่องจากขอบเขตตัวแปรจะถูกสร้างขึ้นเมื่อมีการประกาศฟังก์ชันครั้งแรกและจะคงอยู่ตราบเท่าที่ฟังก์ชันยังคงมีอยู่

aouterเป็นขอบเขตของ ขอบเขตของการมีตัวชี้แม่เพื่อขอบเขตของinner เป็นตัวแปรที่ชี้ไปยัง ยังคงมีอยู่ตราบใดที่ยังมีอยู่ อยู่ภายในการปิดouterfncinnerafnca


116
ฉันคิดว่านี่เป็นตัวอย่างที่ดีและง่ายต่อการเข้าใจ
user12345613

16
ขอบคุณสำหรับคำอธิบายที่ยอดเยี่ยมฉันได้เห็นหลายครั้ง แต่นี่เป็นเวลาที่ฉันได้รับจริงๆ
Dimitar Dimitrov

2
ฉันขอตัวอย่างของวิธีการทำงานในห้องสมุดเช่น JQuery ตามที่ระบุในวรรค 2 ถึงย่อหน้าสุดท้ายได้หรือไม่ ฉันไม่เข้าใจเลย
DPM

6
สวัสดี Jubbat ใช่เปิด jquery.js แล้วดูบรรทัดแรก คุณจะเห็นฟังก์ชั่นเปิดอยู่ ตอนนี้ข้ามไปยังจุดสิ้นสุดคุณจะเห็น window.jQuery = window $ = jQuery จากนั้นฟังก์ชั่นจะปิดและดำเนินการด้วยตนเอง ตอนนี้คุณสามารถเข้าถึงฟังก์ชั่น $ ซึ่งจะเข้าถึงฟังก์ชั่นอื่น ๆ ที่กำหนดไว้ในการปิด นั่นตอบคำถามของคุณหรือไม่
superluminary

4
คำอธิบายที่ดีที่สุดบนเว็บ ง่ายกว่าที่ฉันคิดไว้
ตั๊กแตนตำข้าว

95

ฉันจะให้ตัวอย่าง (ใน JavaScript):

function makeCounter () {
  var count = 0;
  return function () {
    count += 1;
    return count;
  }
}

var x = makeCounter();

x(); returns 1

x(); returns 2

...etc...

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

นี่คือตัวอย่างการแกงของฉันอีกครั้ง:

function add (a) {
  return function (b) {
    return a + b;
  }
}

var add3 = add(3);

add3(4); returns 7

สิ่งที่คุณเห็นคือเมื่อคุณเรียกเพิ่มด้วยพารามิเตอร์ a (ซึ่งคือ 3) ค่านั้นจะอยู่ในการปิดฟังก์ชันที่ส่งคืนซึ่งเรากำหนดให้เป็น add3 ด้วยวิธีนี้เมื่อเราเรียกใช้ add3 จะทราบว่าจะหาค่าที่จะทำการเพิ่มได้อย่างไร


4
IDK ภาษาใด (อาจเป็น F #) ที่คุณใช้ในภาษาด้านบน โปรดยกตัวอย่างข้างต้นเป็น pseudocode ได้ไหม ฉันมีเวลายากที่จะเข้าใจสิ่งนี้
ผู้ใช้


3
@ KyleCronin เป็นตัวอย่างที่ดีขอบคุณ ถาม: ถูกต้องหรือไม่ที่จะพูดว่า "ค่าที่ซ่อนอยู่เรียกว่าการปิด" หรือ "ฟังก์ชันที่ซ่อนค่าคือการปิด" หรือไม่ หรือ "กระบวนการซ่อนค่าคือการปิด"? ขอบคุณ!

2
@ RobertHume เป็นคำถามที่ดี ความหมายคำว่า "การปิด" ค่อนข้างคลุมเครือ คำจำกัดความส่วนบุคคลของฉันคือการรวมกันของทั้งค่าที่ซ่อนอยู่และการใช้ฟังก์ชั่นการปิดล้อมของมันถือเป็นการปิด
Kyle Cronin

1
@ KyleCronin ขอบคุณ - ฉันมีแผนระยะกลางในวันจันทร์ :) ต้องการที่จะมีแนวคิด "ปิด" ที่มั่นคงในหัวของฉัน ขอบคุณสำหรับการโพสต์คำตอบที่ดีสำหรับคำถามของ OP!

58

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


14
ไม่เพียง แต่เป็นสแต็ก แต่เป็นขอบเขตศัพท์ที่ล้อมรอบซึ่งถูกเก็บรักษาไว้ไม่ว่าจะถูกเก็บไว้ในสแต็กหรือฮีป (หรือทั้งสองอย่าง)
Matt Fenwick

38

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

ตัวอย่างเช่นเมื่อคุณมีฟังก์ชั่น JavaScript:

function closed(x) {
  return x + 3;
}

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

แต่ถ้าคุณมีฟังก์ชั่นเช่นนี้:

function open(x) {
  return x*y + 3;
}

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

สิ่งนี้yขอร้องสำหรับคำนิยาม แต่คำนิยามนี้ไม่ได้เป็นส่วนหนึ่งของฟังก์ชั่น - มันถูกนิยามไว้ที่อื่นใน "บริบทแวดล้อม" (หรือที่เรียกว่าสภาพแวดล้อม ) อย่างน้อยนั่นคือสิ่งที่เราหวังไว้: P

ตัวอย่างเช่นสามารถกำหนดได้ทั่วโลก:

var y = 7;

function open(x) {
  return x*y + 3;
}

หรืออาจกำหนดไว้ในฟังก์ชั่นที่ล้อมรอบ:

var global = 2;

function wrapper(y) {
  var w = "unused";

  return function(x) {
    return x*y + 3;
  }
}

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

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

{
  global: 2,
  w: "unused",
  y: [whatever has been passed to that wrapper function as its parameter `y`]
}

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

{
  y: [whatever has been passed to that wrapper function as its parameter `y`]
}

อีกสองสัญลักษณ์ที่กำหนดในสภาพแวดล้อมไม่ได้เป็นส่วนหนึ่งของการปิดฟังก์ชั่นนั้นเพราะมันไม่ต้องการให้เรียกใช้ พวกเขาไม่จำเป็นต้องปิดมัน

ข้อมูลเพิ่มเติมเกี่ยวกับทฤษฎีที่อยู่เบื้องหลังที่นี่: https://stackoverflow.com/a/36878651/434562

เป็นสิ่งที่ควรสังเกตว่าในตัวอย่างข้างต้นฟังก์ชัน wrapper จะส่งกลับฟังก์ชันภายในเป็นค่า ช่วงเวลาที่เราเรียกฟังก์ชั่นนี้สามารถอยู่ในระยะไกลจากช่วงเวลาที่ฟังก์ชั่นได้รับการกำหนด (หรือสร้าง) โดยเฉพาะอย่างยิ่งฟังก์ชั่นการห่อของมันไม่ทำงานอีกต่อไปและพารามิเตอร์ที่อยู่ใน call stack ไม่มีอีกต่อไป: P สิ่งนี้ทำให้เกิดปัญหาเพราะฟังก์ชั่นด้านในจำเป็นต้องyอยู่ที่นั่นเมื่อมันถูกเรียก! กล่าวอีกนัยหนึ่งมันต้องใช้ตัวแปรจากการปิดของมันเพื่ออยู่รอดนานกว่าฟังก์ชั่นกระดาษห่อและอยู่ที่นั่นเมื่อมีความจำเป็น ดังนั้นฟังก์ชั่นด้านในจะต้องทำการถ่ายภาพของตัวแปรเหล่านี้ซึ่งทำการปิดและเก็บไว้ในที่ที่ปลอดภัยสำหรับใช้ในภายหลัง (ที่ไหนสักแห่งนอกสแต็คการโทร)

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


1
การเปรียบเทียบที่อาจช่วยให้ผู้เริ่มต้นทำสิ่งนี้เป็นความสัมพันธ์ที่ปิดท้ายปลายหลวมทั้งหมดซึ่งเป็นสิ่งที่คนทำเมื่อพวกเขาแสวงหาการปิด (หรือแก้ไขการอ้างอิงที่จำเป็นทั้งหมดหรือ ... ) มันช่วยให้ฉันคิดอย่างนั้น: o)
Will Crawford

ฉันอ่านคำจำกัดความของการปิดจำนวนมากในช่วงหลายปีที่ผ่านมา แต่ฉันคิดว่าอันนี้เป็นสิ่งที่ฉันโปรดปรานจนถึงตอนนี้ ฉันเดาว่าพวกเราทุกคนมีวิธีคิดแผนที่ทางจิตใจของตัวเองเช่นนี้
Jason S.

29

การปิดเป็นฟังก์ชันที่สามารถอ้างอิงสถานะในฟังก์ชั่นอื่น ตัวอย่างเช่นใน Python สิ่งนี้ใช้การปิด "inner":

def outer (a):
    b = "variable in outer()"
    def inner (c):
        print a, b, c
    return inner

# Now the return value from outer() can be saved for later
func = outer ("test")
func (1) # prints "test variable in outer() 1

23

เพื่อช่วยอำนวยความสะดวกในการทำความเข้าใจกับการปิดมันอาจจะเป็นประโยชน์ในการตรวจสอบว่าพวกเขาอาจนำไปใช้ในภาษาขั้นตอน คำอธิบายนี้จะเป็นไปตามการใช้งานง่ายของการปิดใน Scheme

ในการเริ่มต้นฉันต้องแนะนำแนวคิดของเนมสเปซ เมื่อคุณป้อนคำสั่งในล่าม Scheme คำสั่งนั้นจะต้องประเมินสัญลักษณ์ต่าง ๆ ในนิพจน์และรับค่าของมัน ตัวอย่าง:

(define x 3)

(define y 4)

(+ x y) returns 7

นิพจน์ define จัดเก็บค่า 3 ในจุดสำหรับ x และค่า 4 ในจุดสำหรับ y จากนั้นเมื่อเราเรียก (+ xy) ล่ามจะค้นหาค่าในเนมสเปซและสามารถดำเนินการและส่งคืน 7

อย่างไรก็ตามใน Scheme มีนิพจน์ที่อนุญาตให้คุณแทนที่ค่าของสัญลักษณ์ชั่วคราว นี่คือตัวอย่าง:

(define x 3)

(define y 4)

(let ((x 5))
   (+ x y)) returns 9

x returns 3

สิ่งที่คำหลักให้ทำแนะนำเนมสเปซใหม่ที่มี x เป็นค่า 5 คุณจะสังเกตเห็นว่ามันยังคงเห็นว่า y คือ 4 ทำให้ผลรวมกลับเป็น 9 คุณยังสามารถเห็นว่าเมื่อนิพจน์สิ้นสุดลง x กลับมาเป็น 3. ในแง่นี้ x ถูกปิดบังชั่วคราวโดยค่าท้องถิ่น

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

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

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

(define x 3)

(define (plus-x y)
  (+ x y))

(let ((x 5))
  (plus-x 4)) returns ?

เรากำหนด x เป็น 3 และบวก -x เป็นพารามิเตอร์ของมัน y และบวกค่าของ x ในที่สุดเราเรียกว่า plus-x ในสภาพแวดล้อมที่ x ถูกหลอกลวงโดย x ใหม่อันนี้มีค่า 5 ถ้าเราเพียงเก็บการดำเนินการ (+ xy) สำหรับฟังก์ชัน plus-x เนื่องจากเราอยู่ในบริบท จาก x เป็น 5 ผลลัพธ์ที่ได้จะเท่ากับ 9 นี่คือสิ่งที่เรียกว่าการกำหนดขอบเขตแบบไดนามิก

อย่างไรก็ตาม Scheme, Common Lisp และภาษาอื่น ๆ อีกมากมายมีสิ่งที่เรียกว่าการกำหนดขอบเขตศัพท์ - นอกเหนือจากการจัดเก็บการดำเนินการ (+ xy) เรายังเก็บ namespace ณ จุดนั้น ด้วยวิธีนี้เมื่อเราค้นหาค่าเราจะเห็นว่า x ในบริบทนี้เป็นจริง 3 นี่คือการปิด

(define x 3)

(define (plus-x y)
  (+ x y))

(let ((x 5))
  (plus-x 4)) returns 7

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


โอเคขอบคุณคำตอบของคุณฉันคิดว่าในที่สุดฉันก็มีความคิดว่าการปิดเป็นเรื่องเกี่ยวกับอะไร แต่มีคำถามหนึ่งคำถามใหญ่คือ "เราสามารถใช้ลิสต์ที่เชื่อมโยงเพื่อเก็บสถานะของเนมสเปซในช่วงเวลาของการกำหนดฟังก์ชั่นทำให้เราสามารถเข้าถึงตัวแปรที่มิฉะนั้นจะไม่อยู่ในขอบเขต" Why do we want to access variables that are out of scope? when we say let x = 5, we want x to be 5 and not 3. What is happening?
Lazer

@ เลเซอร์: ขออภัยประโยคนั้นไม่สมเหตุสมผลดังนั้นฉันจึงอัปเดต ฉันหวังว่ามันจะสมเหตุสมผลมากกว่านี้ นอกจากนี้อย่าคิดว่ารายชื่อที่เชื่อมโยงเป็นรายละเอียดการนำไปใช้ (เนื่องจากไม่มีประสิทธิภาพมาก) แต่เป็นวิธีง่ายๆในการกำหนดแนวคิดว่าจะทำอย่างไร
Kyle Cronin

10

นี่คือตัวอย่างโลกแห่งความจริงที่ว่าทำไม Closures kick ass ... นี่มันตรงจากโค้ด Javascript ของฉัน ให้ฉันอธิบาย

Function.prototype.delay = function(ms /*[, arg...]*/) {
  var fn = this,
      args = Array.prototype.slice.call(arguments, 1);

  return window.setTimeout(function() {
      return fn.apply(fn, args);
  }, ms);
};

และนี่คือวิธีที่คุณจะใช้:

var startPlayback = function(track) {
  Player.play(track);  
};
startPlayback(someTrack);

ตอนนี้จินตนาการว่าคุณต้องการให้การเล่นเริ่มล่าช้าเช่นเช่น 5 วินาทีในภายหลังหลังจากที่โค้ดขนาดสั้นนี้ทำงาน เป็นเรื่องง่ายด้วยdelayและมันปิด:

startPlayback.delay(5000, someTrack);
// Keep going, do other things

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

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


1
ควรสังเกตว่าการขยายภาษาหรือวัตถุโฮสต์โดยทั่วไปถือว่าเป็นสิ่งไม่ดีเนื่องจากเป็นส่วนหนึ่งของเนมสเปซส่วนกลาง
Jon Cooke

9

ฟังก์ชั่นที่ไม่มีตัวแปรอิสระเรียกว่าฟังก์ชั่นแท้

ฟังก์ชั่นที่มีตัวแปรอิสระหนึ่งตัวหรือมากกว่านั้นเรียกว่าการปิด

var pure = function pure(x){
  return x 
  // only own environment is used
}

var foo = "bar"

var closure = function closure(){
  return foo 
  // foo is a free variable from the outer environment
}

src: https://leanpub.com/javascriptallongesix/read#leanpub-auto-if-functions-without-free-variables-are-pure-are-closures-impure


ทำไมสิ่งนี้ถึงเล็กลง? จริง ๆ แล้วมันเป็น "บนเส้นทางที่ถูกต้อง" ที่มีความแตกต่างในตัวแปรอิสระและตัวแปรที่ถูกผูกไว้และฟังก์ชั่นบริสุทธิ์ / ปิดและฟังก์ชั่นที่ไม่บริสุทธิ์ / เปิดฟังก์ชั่นกว่าคำตอบ clueless อื่น ๆ ที่นี่: P (ลดราคา ถูกปิด)
SasQ

ฉันไม่มีความคิดจริงๆ นี่คือเหตุผลที่ StackOverflow ดูด เพียงแค่ดูที่มาของคำตอบของฉัน ใครจะเถียงกับเรื่องนี้?
soundyogi

ดังนั้นไม่ดูดและฉันไม่เคยได้ยินคำว่า "ตัวแปรอิสระ"
Kai

เป็นการยากที่จะพูดคุยเกี่ยวกับการปิดโดยไม่พูดถึงตัวแปรฟรี แค่มองพวกเขา คำศัพท์ CS มาตรฐาน
ComDubh

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

7

TL; DR

การปิดคือฟังก์ชันและขอบเขตที่กำหนดให้กับ (หรือใช้เป็น) ตัวแปร ดังนั้นการปิดชื่อ: ขอบเขตและฟังก์ชั่นถูกปิดล้อมและใช้งานเหมือนกับเอนทิตีอื่น ๆ

คำอธิบายลักษณะเชิงลึกของ Wikipedia

ตามวิกิพีเดียการปิดคือ:

เทคนิคการใช้การผูกชื่อขอบเขตที่ จำกัด ในภาษาด้วยฟังก์ชันชั้นหนึ่ง

นั่นหมายความว่าอย่างไร? ให้ดูคำจำกัดความบางอย่าง

ฉันจะอธิบายการปิดและคำจำกัดความอื่น ๆ ที่เกี่ยวข้องโดยใช้ตัวอย่างนี้:

function startAt(x) {
    return function (y) {
        return x + y;
    }
}

var closure1 = startAt(1);
var closure2 = startAt(5);

console.log(closure1(3)); // 4 (x == 1, y == 3)
console.log(closure2(3)); // 8 (x == 5, y == 3)

ฟังก์ชั่นชั้นหนึ่ง

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

ในตัวอย่างข้างต้นstartAtส่งกลับ ( ไม่ระบุชื่อ ) ฟังก์ชั่นการทำงานที่ได้รับมอบหมายให้และclosure1 closure2ดังนั้นในขณะที่คุณเห็น JavaScript ปฏิบัติต่อฟังก์ชั่นเหมือนกับเอนทิตีอื่น ๆ (พลเมืองชั้นหนึ่ง)

การผูกชื่อ

การผูกชื่อเป็นเรื่องเกี่ยวกับการค้นหาข้อมูลที่อ้างอิงตัวแปร (ตัวระบุ)อ้างอิงขอบเขตมีความสำคัญจริง ๆ ที่นี่เนื่องจากเป็นสิ่งที่จะกำหนดวิธีแก้ปัญหาการรวม

ในตัวอย่างด้านบน:

  • ในขอบเขตของฟังก์ชั่นไม่ระบุชื่อภายในyถูกผูกไว้กับ3ถูกผูกไว้กับ
  • ในstartAtขอบเขตของxถูกผูกไว้กับ1หรือ5(ขึ้นอยู่กับการปิด)

ภายในขอบเขตของฟังก์ชันที่ไม่ระบุชื่อxจะไม่ถูกผูกไว้กับค่าใด ๆ ดังนั้นจึงจำเป็นต้องแก้ไขในstartAtขอบเขตด้านบน

การกำหนดขอบเขตคำศัพท์

ตามที่Wikipedia กล่าวว่าขอบเขต:

เป็นพื้นที่ของโปรแกรมคอมพิวเตอร์ที่มีผลผูกพันเป็นที่ถูกต้อง: ที่ชื่อสามารถนำมาใช้ในการอ้างถึงกิจการ

มีสองเทคนิค:

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

สำหรับคำอธิบายเพิ่มเติมตรวจสอบคำถามนี้และจะดูที่วิกิพีเดีย

ในตัวอย่างข้างต้นเราจะเห็นว่า JavaScript ถูกกำหนดขอบเขตแบบ lexically เนื่องจากเมื่อxแก้ไขแล้วการโยงจะถูกค้นหาในstartAtขอบเขตด้านบนตามรหัสแหล่งที่มา (ฟังก์ชันที่ไม่ระบุตัวตนที่มองหา x ถูกกำหนดไว้ภายในstartAt) และ ไม่ขึ้นกับ call stack วิธี (ขอบเขตที่) ฟังก์ชั่นถูกเรียก

การห่อ (ปิด) ขึ้น

ในตัวอย่างของเราเมื่อเราเรียกstartAtมันจะส่งคืนฟังก์ชัน (ชั้นหนึ่ง) ที่จะถูกกำหนดให้closure1และclosure2ทำให้การปิดถูกสร้างขึ้นเนื่องจากตัวแปรที่ส่งผ่าน1และ5จะถูกบันทึกภายในstartAtขอบเขตของที่จะถูกล้อมรอบด้วยการส่งคืน ฟังก์ชั่นไม่ระบุชื่อ เมื่อเราเรียกใช้ฟังก์ชันที่ไม่ระบุชื่อนี้ผ่านclosure1และclosure2ด้วยอาร์กิวเมนต์เดียวกัน ( 3) yจะพบค่าของทันที (ซึ่งเป็นพารามิเตอร์ของฟังก์ชันนั้น) แต่xไม่ได้อยู่ในขอบเขตของฟังก์ชันที่ไม่ระบุชื่อดังนั้นการแก้ปัญหาจึงดำเนินต่อไป ฟังก์ชั่นขอบเขตบน (ศัพท์) (ที่ถูกบันทึกไว้ในการปิด) ซึ่งxพบว่าถูกผูกไว้กับทั้ง1หรือ5. ตอนนี้เรารู้ทุกอย่างสำหรับการรวมเพื่อให้ผลลัพธ์สามารถส่งคืนแล้วพิมพ์

ตอนนี้คุณควรเข้าใจการปิดและวิธีการทำงานซึ่งเป็นส่วนพื้นฐานของ JavaScript

ความดีความชอบ

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


5

การปิดเป็นคุณลักษณะใน JavaScript ที่ฟังก์ชันมีการเข้าถึงตัวแปรขอบเขตของตัวเองการเข้าถึงตัวแปรฟังก์ชันภายนอกและการเข้าถึงตัวแปรส่วนกลาง

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

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

ตัวอย่างของการปิด :

var globalValue = 5;

function functOuter() {
  var outerFunctionValue = 10;

  //Inner function has access to the outer function value
  //and the global variables
  function functInner() {
    var innerFunctionValue = 5;
    alert(globalValue + outerFunctionValue + innerFunctionValue);
  }
  functInner();
}
functOuter();  

เอาต์พุตจะเป็น 20 ซึ่งผลรวมของตัวแปรภายในฟังก์ชั่นของตัวเองตัวแปรฟังก์ชั่นด้านนอกและค่าตัวแปรทั่วโลก


4

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

def n_times(a_thing)
  return lambda{|n| a_thing * n}
end

ในรหัสข้างต้นlambda(|n| a_thing * n}คือการปิดเพราะa_thingถูกเรียกโดยแลมบ์ดา (ผู้สร้างฟังก์ชั่นที่ไม่ระบุชื่อ)

ตอนนี้ถ้าคุณใส่ฟังก์ชั่นที่ไม่ระบุชื่อที่เกิดขึ้นในตัวแปรฟังก์ชั่น

foo = n_times(4)

foo จะทำลายกฎการกำหนดขอบเขตปกติและเริ่มใช้ 4 ภายใน

foo.call(3)

ผลตอบแทน 12


2

ในระยะสั้นตัวชี้ฟังก์ชั่นเป็นเพียงตัวชี้ไปยังตำแหน่งในฐานรหัสโปรแกรม (เช่นตัวนับโปรแกรม) ในขณะที่ปิด = ตัวชี้ฟังก์ชั่น + กรอบสแต็ค

.


1

•การปิดเป็นโปรแกรมย่อยและสภาพแวดล้อมการอ้างอิงที่ถูกกำหนดไว้

- สภาพแวดล้อมการอ้างอิงเป็นสิ่งจำเป็นหากโปรแกรมย่อยสามารถเรียกได้จากที่ใดก็ได้ในโปรแกรม

- ภาษาที่มีขอบเขตคงที่ที่ไม่อนุญาตให้โปรแกรมย่อยซ้อนกันไม่จำเป็นต้องปิด

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

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

ตัวอย่าง

function makeAdder(x) {
return function(y) {return x + y;}
}
var add10 = makeAdder(10);
var add5 = makeAdder(5);
document.write(″add 10 to 20: ″ + add10(20) +
″<br />″);
document.write(″add 5 to 20: ″ + add5(20) +
″<br />″);

0

นี่คืออีกตัวอย่างในชีวิตจริงและการใช้ภาษาสคริปต์ที่เป็นที่นิยมในเกม - Lua ฉันต้องการเปลี่ยนแปลงวิธีการทำงานของไลบรารีเล็กน้อยเพื่อหลีกเลี่ยงปัญหาที่ stdin ไม่สามารถใช้งานได้

local old_dofile = dofile

function dofile( filename )
  if filename == nil then
    error( 'Can not use default of stdin.' )
  end

  old_dofile( filename )
end

ค่าของ old_dofile จะหายไปเมื่อบล็อกของรหัสนี้เสร็จสิ้นขอบเขต (เพราะอยู่ในเครื่อง) แต่ค่านั้นถูกปิดไว้ดังนั้นฟังก์ชัน dofile ที่นิยามใหม่ใหม่สามารถเข้าถึงได้หรือคัดลอกเก็บไว้พร้อมกับฟังก์ชันเป็น 'upvalue'


0

จากLua.org :

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


0

หากคุณมาจากโลก Java คุณสามารถเปรียบเทียบการปิดด้วยฟังก์ชันสมาชิกของคลาส ดูตัวอย่างนี้

var f=function(){
  var a=7;
  var g=function(){
    return a;
  }
  return g;
}

ฟังก์ชั่นgคือการปิด: gปิดaดังนั้นจึงgสามารถนำมาเปรียบเทียบกับฟังก์ชั่นสมาชิกaสามารถนำมาเปรียบเทียบกับสนามคลาสและฟังก์ชั่นที่fมีชั้นเรียน


0

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

Listing 2-18:
    function outerFunction(arg) {
     var variableInOuterFunction = arg;

     function bar() {
             console.log(variableInOuterFunction); // Access a variable from the outer scope
     }
     // Call the local function to demonstrate that it has access to arg
     bar(); 
    }
    outerFunction('hello closure!'); // logs hello closure!

แหล่งที่มา: http://index-of.es/Varios/Basarat%20Ali%20Syed%20(auth.)-Beginning%20Node.js-Apress%20(2014).pdf


0

โปรดดูรหัสด้านล่างเพื่อทำความเข้าใจการปิดในเชิงลึกยิ่งขึ้น:

        for(var i=0; i< 5; i++){            
            setTimeout(function(){
                console.log(i);
            }, 1000);                        
        }

นี่คือสิ่งที่จะส่งออก? 0,1,2,3,4ไม่ว่าจะเป็น5,5,5,5,5เพราะการปิด

ดังนั้นมันจะแก้ปัญหาอย่างไร คำตอบอยู่ด้านล่าง:

       for(var i=0; i< 5; i++){
           (function(j){     //using IIFE           
                setTimeout(function(){
                               console.log(j);
                           },1000);
            })(i);          
        }

ให้ฉันอธิบายง่ายๆเมื่อฟังก์ชั่นที่สร้างขึ้นไม่มีอะไรเกิดขึ้นจนกว่ามันจะเรียกว่าวนรอบในรหัสที่ 1 เรียกว่า 5 ครั้ง แต่ไม่ได้เรียกทันทีดังนั้นเมื่อมันเรียกว่าหลังจากหลังจาก 1 วินาทีและนี่คืออะซิงโครนัส ใน var i และในที่สุดก็ดำเนินการ setTimeoutฟังก์ชั่นห้าครั้งและพิมพ์5,5,5,5,5

นี่คือวิธีการแก้ปัญหาโดยใช้ IIFE เช่นการแสดงออกของฟังก์ชันการเรียกใช้ทันที

       (function(j){  //i is passed here           
            setTimeout(function(){
                           console.log(j);
                       },1000);
        })(i);  //look here it called immediate that is store i=0 for 1st loop, i=1 for 2nd loop, and so on and print 0,1,2,3,4

สำหรับข้อมูลเพิ่มเติมโปรดเข้าใจบริบทการดำเนินการเพื่อให้เข้าใจการปิด

  • มีวิธีแก้ปัญหาอีกหนึ่งวิธีในการแก้ปัญหานี้โดยใช้ let (คุณสมบัติ ES6) แต่ภายใต้ประทุนเหนือฟังก์ชันใช้งานได้

     for(let i=0; i< 5; i++){           
         setTimeout(function(){
                        console.log(i);
                    },1000);                        
     }
    
    Output: 0,1,2,3,4
    

=> คำอธิบายเพิ่มเติม:

ในหน่วยความจำเมื่อลูปรันรูปภาพทำดังนี้

ห่วง 1)

     setTimeout(function(){
                    console.log(i);
                },1000);  

วน 2)

     setTimeout(function(){
                    console.log(i);
                },1000); 

ห่วง 3)

     setTimeout(function(){
                    console.log(i);
                },1000); 

ห่วง 4)

     setTimeout(function(){
                    console.log(i);
                },1000); 

วง 5)

     setTimeout(function(){
                    console.log(i);
                },1000);  

ที่นี่ฉันไม่ได้ถูกเรียกใช้และหลังจากวนรอบเสร็จสมบูรณ์ var i ที่เก็บค่า 5 ในหน่วยความจำ แต่ขอบเขตจะมองเห็นได้เสมอในฟังก์ชันลูก ๆ ดังนั้นเมื่อฟังก์ชันทำงานภายใน setTimeoutห้าครั้งที่พิมพ์5,5,5,5,5

ดังนั้นเพื่อแก้ไขปัญหานี้ให้ใช้ IIFE ตามที่อธิบายไว้ข้างต้น


ขอบคุณสำหรับคำตอบ. มันจะอ่านง่ายขึ้นถ้าคุณแยกรหัสออกจากคำอธิบาย (อย่าเยื้องบรรทัดที่ไม่ใช่รหัส)
eMBee

0

Currying: ช่วยให้คุณสามารถประเมินฟังก์ชั่นบางส่วนได้โดยส่งผ่านเพียงเซ็ตย่อยของอาร์กิวเมนต์ พิจารณาสิ่งนี้:

function multiply (x, y) {
  return x * y;
}

const double = multiply.bind(null, 2);

const eight = double(4);

eight == 8;

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

function apple(x){
   function google(y,z) {
    console.log(x*y);
   }
   google(7,2);
}

apple(3);

// the answer here will be 21

0

ปิดง่ายมาก เราสามารถพิจารณาได้ดังนี้: Closure = function + environment lexical

พิจารณาฟังก์ชั่นต่อไปนี้:

function init() {
    var name = “Mozilla”;
}

สิ่งที่จะปิดในกรณีข้างต้น? Function init () และตัวแปรในสภาพแวดล้อมของคำศัพท์เช่นชื่อ ปิด = init () + ชื่อ

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

function init() {
    var name = “Mozilla”;
    function displayName(){
        alert(name);
}
displayName();
}

สิ่งที่จะปิดที่นี่? ฟังก์ชั่นภายในสามารถเข้าถึงตัวแปรของฟังก์ชั่นด้านนอก displayName () สามารถเข้าถึงชื่อตัวแปรที่ประกาศในฟังก์ชัน parent, init () อย่างไรก็ตามตัวแปรโลคัลเดียวกันใน displayName () จะถูกใช้หากมีอยู่

การปิด 1:ฟังก์ชั่น init + (ชื่อตัวแปร + ฟังก์ชั่น displayName ()) -> ขอบเขตคำศัพท์

การปิด 2:ฟังก์ชั่น displayName + (ตัวแปรชื่อ) -> ขอบเขตคำศัพท์


0

ปิดให้ JavaScript กับรัฐ

สถานะในการเขียนโปรแกรมหมายถึงการจดจำสิ่งต่าง ๆ

ตัวอย่าง

var a = 0;

a = a + 1; // => 1
a = a + 1; // => 2
a = a + 1; // => 3

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

บ่อยครั้งในภาษาการเขียนโปรแกรมคุณต้องการติดตามสิ่งต่างๆจดจำข้อมูลและเข้าถึงได้ในภายหลัง

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

ตัวอย่าง

class Bread {
  constructor (weight) {
    this.weight = weight;
  }

  render () {
    return `My weight is ${this.weight}!`;
  }
}

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

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

ตัวอย่าง

var n = 0;
var count = function () {
  n = n + 1;
  return n;
};

count(); // # 1
count(); // # 2
count(); // # 3

ตัวอย่างข้างต้นบรรลุเป้าหมายของ "การรักษาสถานะ" ด้วยตัวแปร มันเยี่ยมมาก! อย่างไรก็ตามสิ่งนี้มีข้อเสียที่ตัวแปร (ผู้ถือ "สถานะ") ถูกเปิดเผย เราทำได้ดีกว่า เราสามารถใช้การปิด

ตัวอย่าง

var countGenerator = function () {
  var n = 0;
  var count = function () {
    n = n + 1;
    return n;
  };

  return count;
};

var count = countGenerator();
count(); // # 1
count(); // # 2
count(); // # 3

มันยอดเยี่ยมมาก

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

ทำไมเรื่องนี้ถึงน่าอัศจรรย์? เนื่องจากไม่เกี่ยวข้องกับเครื่องมือที่ซับซ้อนและซับซ้อนอื่น ๆ (เช่นคลาสวิธีการอินสแตนซ์ ฯลฯ ) เราสามารถ 1. ปกปิด 2. ควบคุมจากระยะไกล

เราปกปิดสถานะตัวแปร "n" ซึ่งทำให้เป็นตัวแปรส่วนตัว! นอกจากนี้เรายังได้สร้าง API ที่สามารถควบคุมตัวแปรนี้ในแบบที่กำหนดไว้ล่วงหน้า โดยเฉพาะอย่างยิ่งเราสามารถเรียก API ได้ว่า "count ()" และนั่นจะเพิ่ม 1 ถึง "n" จาก "ระยะทาง" ไม่ว่าในทางใดรูปร่างหรือรูปแบบที่ทุกคนจะสามารถเข้าถึง "n" ยกเว้นผ่าน API

JavaScript นั้นยอดเยี่ยมมากในความเรียบง่าย

การปิดเป็นส่วนสำคัญของสาเหตุที่เป็นเช่นนี้


0

ตัวอย่างง่ายๆใน Groovy สำหรับการอ้างอิงของคุณ:

def outer() {
    def x = 1
    return { -> println(x)} // inner
}
def innerObj = outer()
innerObj() // prints 1
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.