ฉันถามคำถามเกี่ยวกับการปิดบัญชีและการปิดบัญชีถูกกล่าวถึง การปิดคืออะไร มันเกี่ยวข้องกับแกงอย่างไร?
ฉันถามคำถามเกี่ยวกับการปิดบัญชีและการปิดบัญชีถูกกล่าวถึง การปิดคืออะไร มันเกี่ยวข้องกับแกงอย่างไร?
คำตอบ:
เมื่อคุณประกาศตัวแปรท้องถิ่นตัวแปรนั้นมีขอบเขต โดยทั่วไปแล้วตัวแปรโลคัลจะมีอยู่ภายในบล็อกหรือฟังก์ชันที่คุณประกาศเท่านั้น
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
ยังคงมีอยู่เนื่องจากขอบเขตตัวแปรจะถูกสร้างขึ้นเมื่อมีการประกาศฟังก์ชันครั้งแรกและจะคงอยู่ตราบเท่าที่ฟังก์ชันยังคงมีอยู่
a
outer
เป็นขอบเขตของ ขอบเขตของการมีตัวชี้แม่เพื่อขอบเขตของinner
เป็นตัวแปรที่ชี้ไปยัง ยังคงมีอยู่ตราบใดที่ยังมีอยู่ อยู่ภายในการปิดouter
fnc
inner
a
fnc
a
ฉันจะให้ตัวอย่าง (ใน 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 จะทราบว่าจะหาค่าที่จะทำการเพิ่มได้อย่างไร
คำตอบของไคล์ค่อนข้างดี ฉันคิดว่าคำชี้แจงเพิ่มเติมเพียงอย่างเดียวคือการปิดเป็นสแนปชอตของสแต็ก ณ จุดที่สร้างแลมด้าฟังก์ชัน จากนั้นเมื่อฟังก์ชันถูกเรียกใช้งานอีกครั้งสแต็กจะถูกเรียกคืนเป็นสถานะนั้นก่อนที่จะเรียกใช้งานฟังก์ชัน ดังนั้นตามที่ไคล์กล่าวถึงค่าที่ซ่อน ( count
) จะใช้ได้เมื่อฟังก์ชันแลมบ์ดาดำเนินการ
ก่อนอื่นตรงกันข้ามกับสิ่งที่คนส่วนใหญ่บอกคุณว่าการปิดไม่ใช่ฟังก์ชั่น ! แล้วมันคืออะไร
มันเป็นชุดของสัญลักษณ์ที่กำหนดไว้ใน "บริบทโดยรอบ" ของฟังก์ชัน (เรียกว่าสภาพแวดล้อม ) ซึ่งทำให้เป็นนิพจน์ที่ปิด (นั่นคือนิพจน์ที่ทุกสัญลักษณ์ถูกกำหนดและมีค่าดังนั้นจึงสามารถประเมินได้)
ตัวอย่างเช่นเมื่อคุณมีฟังก์ชั่น 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
อยู่ที่นั่นเมื่อมันถูกเรียก! กล่าวอีกนัยหนึ่งมันต้องใช้ตัวแปรจากการปิดของมันเพื่ออยู่รอดนานกว่าฟังก์ชั่นกระดาษห่อและอยู่ที่นั่นเมื่อมีความจำเป็น ดังนั้นฟังก์ชั่นด้านในจะต้องทำการถ่ายภาพของตัวแปรเหล่านี้ซึ่งทำการปิดและเก็บไว้ในที่ที่ปลอดภัยสำหรับใช้ในภายหลัง (ที่ไหนสักแห่งนอกสแต็คการโทร)
และนี่คือสาเหตุที่ผู้คนมักสับสนคำว่าปิดเป็นฟังก์ชันชนิดพิเศษที่สามารถทำภาพรวมของตัวแปรภายนอกที่ใช้หรือโครงสร้างข้อมูลที่ใช้เก็บตัวแปรเหล่านี้ในภายหลัง แต่ฉันหวังว่าคุณจะเข้าใจในขณะนี้ว่าไม่ได้ปิดตัวเอง - พวกเขาเป็นเพียงวิธีการใช้การปิดในภาษาการเขียนโปรแกรมหรือกลไกภาษาที่ช่วยให้ตัวแปรจากการปิดฟังก์ชั่นที่จะมีเมื่อจำเป็น มีความเข้าใจผิดมากมายเกี่ยวกับการปิดที่ (โดยไม่จำเป็น) ทำให้เรื่องนี้สับสนและซับซ้อนมากขึ้นกว่าที่เป็นจริง
การปิดเป็นฟังก์ชันที่สามารถอ้างอิงสถานะในฟังก์ชั่นอื่น ตัวอย่างเช่นใน 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
เพื่อช่วยอำนวยความสะดวกในการทำความเข้าใจกับการปิดมันอาจจะเป็นประโยชน์ในการตรวจสอบว่าพวกเขาอาจนำไปใช้ในภาษาขั้นตอน คำอธิบายนี้จะเป็นไปตามการใช้งานง่ายของการปิดใน 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?
นี่คือตัวอย่างโลกแห่งความจริงที่ว่าทำไม 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
ด้วย5000
ms ข้อมูลโค้ดแรกจะทำงานและเก็บอาร์กิวเมนต์ที่ส่งเป็นอาร์กิวเมนต์ในขณะที่ปิด จากนั้น 5 วินาทีในภายหลังเมื่อการsetTimeout
โทรกลับเกิดขึ้นการปิดยังคงรักษาตัวแปรเหล่านั้นเพื่อให้สามารถเรียกใช้ฟังก์ชั่นดั้งเดิมด้วยพารามิเตอร์ดั้งเดิมได้
นี่คือประเภทของการแกงหรือการตกแต่งฟังก์ชั่น
หากไม่มีการปิดคุณจะต้องรักษาตัวแปรเหล่านั้นให้อยู่นอกฟังก์ชั่นดังนั้นจึงต้องทิ้งโค้ดที่อยู่นอกฟังก์ชั่นด้วยสิ่งที่มีเหตุผลอยู่ภายใน การใช้การปิดสามารถปรับปรุงคุณภาพและการอ่านรหัสของคุณได้อย่างมาก
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
}
การปิดคือฟังก์ชันและขอบเขตที่กำหนดให้กับ (หรือใช้เป็น) ตัวแปร ดังนั้นการปิดชื่อ: ขอบเขตและฟังก์ชั่นถูกปิดล้อมและใช้งานเหมือนกับเอนทิตีอื่น ๆ
เทคนิคการใช้การผูกชื่อขอบเขตที่ จำกัด ในภาษาด้วยฟังก์ชันชั้นหนึ่ง
นั่นหมายความว่าอย่างไร? ให้ดูคำจำกัดความบางอย่าง
ฉันจะอธิบายการปิดและคำจำกัดความอื่น ๆ ที่เกี่ยวข้องโดยใช้ตัวอย่างนี้:
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เป็นเรื่องเกี่ยวกับ: คุณใช้ฟังก์ชั่น (ปิด) เพื่อส่งผ่านแต่ละอาร์กิวเมนต์ของการดำเนินการแทนการใช้หนึ่งฟังก์ชั่นที่มีพารามิเตอร์หลายตัว
การปิดเป็นคุณลักษณะใน 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 ซึ่งผลรวมของตัวแปรภายในฟังก์ชั่นของตัวเองตัวแปรฟังก์ชั่นด้านนอกและค่าตัวแปรทั่วโลก
ในสถานการณ์ปกติตัวแปรถูกผูกมัดโดยกฎการกำหนดขอบเขต: ตัวแปรโลคัลทำงานภายในฟังก์ชันที่กำหนดเท่านั้น การปิดเป็นวิธีหนึ่งในการทำลายกฎนี้ชั่วคราวเพื่อความสะดวก
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
ในระยะสั้นตัวชี้ฟังก์ชั่นเป็นเพียงตัวชี้ไปยังตำแหน่งในฐานรหัสโปรแกรม (เช่นตัวนับโปรแกรม) ในขณะที่ปิด = ตัวชี้ฟังก์ชั่น + กรอบสแต็ค
.
•การปิดเป็นโปรแกรมย่อยและสภาพแวดล้อมการอ้างอิงที่ถูกกำหนดไว้
- สภาพแวดล้อมการอ้างอิงเป็นสิ่งจำเป็นหากโปรแกรมย่อยสามารถเรียกได้จากที่ใดก็ได้ในโปรแกรม
- ภาษาที่มีขอบเขตคงที่ที่ไม่อนุญาตให้โปรแกรมย่อยซ้อนกันไม่จำเป็นต้องปิด
- การปิดเป็นสิ่งจำเป็นเฉพาะเมื่อโปรแกรมย่อยสามารถเข้าถึงตัวแปรในขอบเขตการซ้อนและสามารถเรียกใช้ได้จากทุกที่
- เพื่อสนับสนุนการปิดการใช้งานอาจจำเป็นต้องกำหนดขอบเขตให้กับตัวแปรบางตัวอย่างไม่ จำกัด (เนื่องจากโปรแกรมย่อยอาจเข้าถึงตัวแปรที่ไม่ใช่ท้องถิ่นซึ่งโดยปกติแล้วจะไม่มีชีวิตอีกต่อไป)
ตัวอย่าง
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 />″);
นี่คืออีกตัวอย่างในชีวิตจริงและการใช้ภาษาสคริปต์ที่เป็นที่นิยมในเกม - 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'
จากLua.org :
เมื่อฟังก์ชั่นถูกเขียนไว้ในฟังก์ชั่นอื่นมันมีการเข้าถึงตัวแปรท้องถิ่นจากฟังก์ชั่นการปิดล้อม; คุณสมบัตินี้เรียกว่าการกำหนดขอบเขตคำศัพท์ แม้ว่ามันอาจฟังดูชัดเจน แต่ก็ไม่เป็นเช่นนั้น การกำหนดขอบเขตคำศัพท์และฟังก์ชั่นชั้นหนึ่งเป็นแนวคิดที่ทรงพลังในภาษาการเขียนโปรแกรม แต่มีเพียงไม่กี่ภาษาที่สนับสนุนแนวคิดนั้น
หากคุณมาจากโลก Java คุณสามารถเปรียบเทียบการปิดด้วยฟังก์ชันสมาชิกของคลาส ดูตัวอย่างนี้
var f=function(){
var a=7;
var g=function(){
return a;
}
return g;
}
ฟังก์ชั่นg
คือการปิด: g
ปิดa
ดังนั้นจึงg
สามารถนำมาเปรียบเทียบกับฟังก์ชั่นสมาชิกa
สามารถนำมาเปรียบเทียบกับสนามคลาสและฟังก์ชั่นที่f
มีชั้นเรียน
การปิดเมื่อใดก็ตามที่เรามีฟังก์ชั่นที่กำหนดไว้ในฟังก์ชั่นอื่นฟังก์ชั่นภายในมีการเข้าถึงตัวแปรที่ประกาศไว้ในฟังก์ชั่นด้านนอก การอธิบายการปิดบัญชีด้วยตัวอย่างที่ดีที่สุด ในรายการ 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
โปรดดูรหัสด้านล่างเพื่อทำความเข้าใจการปิดในเชิงลึกยิ่งขึ้น:
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 ตามที่อธิบายไว้ข้างต้น
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
ปิดง่ายมาก เราสามารถพิจารณาได้ดังนี้: 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 + (ตัวแปรชื่อ) -> ขอบเขตคำศัพท์
สถานะในการเขียนโปรแกรมหมายถึงการจดจำสิ่งต่าง ๆ
ตัวอย่าง
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
การปิดเป็นส่วนสำคัญของสาเหตุที่เป็นเช่นนี้
ตัวอย่างง่ายๆใน Groovy สำหรับการอ้างอิงของคุณ:
def outer() {
def x = 1
return { -> println(x)} // inner
}
def innerObj = outer()
innerObj() // prints 1