ฟังก์ชันในฟังก์ชัน
ค่าที่มีการคืนกลับด้วยคำสั่ง return ในฟังก์ชันนั้นจะเป็นข้อมูลชนิดใดก็ได้
รวมถึงฟังก์ชัน
และฟังก์ชันก็สามารถถูกนิยามขึ้นภายในฟังก์ชันได้ด้วย
ถ้าสิ่งที่คืนกลับนั้นเป็นฟังก์ชันที่ถูกนิยามในฟังก์ชันอีกทีจะมีความพิเศษอยู่สักหน่อยซึ่งจะมาดูกันในบทนี้
ยกตัวอย่าง
var fNok = function() {
var fNai = function(x) {
return x * 2;
};
return fNai;
};
var f1 = fNok();
alert(f1(1.1)); // ได้ 2.2
ในที่นี้มีความซับซ้อนสักหน่อย โดยมีการนิยามฟังก์ชัน fNai ขึ้นในฟังก์ชัน fNok
แล้วให้ฟังก์ชันนี้เป็นค่าคืนกลับ
ดังนั้นถ้าใช้ฟังก์ชัน fNok ก็จะได้ฟังก์ชัน fNai มา ในที่นี้เก็บไว้ในตัวแปร f1
แล้วก็นำมาใช้ พบว่าฟังก์ชันนั้นทำงานได้
นอกจากนี้ยังสามารถเรียก fNok เพื่อสร้าง fNai แล้วก็เรียกใช้ต่อทันที
แบบนี้โครงสร้างจะเป็นวงเล็บซ้อน
ซึ่งอาจดูแปลกๆแต่ก็เป็นสิ่งที่อาจพบได้บ่อยๆในโค้ดจาวาสคริปต์
alert(fNok()(0.4)); // ได้ 0.8
แน่นอนว่าเรียก fNai จากด้านนอกฟังก์ชัน fNok โดยตรงไม่ได้
เพราะเป็นสิ่งที่ถูกนิยามด้านมน fNok
alert(fNai(0.6)); // ได้ ReferenceError: fNai is not defined
จนถึงตรงนี้อาจดูซับซ้อนเล็กน้อย
แต่หากไล่คิดตามลำดับขั้นตามเหตุตามผลก็ไม่ได้มีอะไรน่าแปลก
แต่ที่จะเข้าใจยากขึ้นคือตัวอย่างถัดไป
เมื่อฟังก์ชันในฟังก์ชันเรียกใช้ตัวแปรในฟังก์ชัน
ลองแก้ใหม่เล็กน้อยจากตัวอย่างที่แล้วเป็นดังนี้
var fNok = function() {
var k = 10;
var fNai = function(x) {
return x * k;
};
return fNai;
};
var f2 = fNok();
ข้อแตกต่างคือคราวนี้มีการนิยามตัวแปร k ขึ้นมาในฟังก์ชัน fNok
และตัวแปรนี้ถูกเรียกใข้ใน fNai จากนั้น fNai ก็ถูกส่งเป็นค่าคืนกลับ
ตัวแปร f2 เรียกใช้ fNok และได้ฟังก์ชัน fNai ที่อยู่ด้านในมาเก็บไว้
ปัญหาคือ หากตอนนี้เราเรียกใช้ฟังก์ชัน f2 จะเกิดอะไรขึ้น?
ฟังก์ชัน fNai นั้นจะต้องใช้ตัวแปร k ซึ่งถูกนิยามไว้ภายในฟังก์ชัน fNok
แต่ตอนนี้เรากำลังจะเรียกมันนอกฟังก์ชัน fNok ดังนั้นจึงน่าเป็นห่วงว่าตัวแปร k
จะถูกเรียกใช้ได้หรือ
เพราะตัวแปรที่นิยามในฟังก์ชันจะถูกทำลายทิ้งหลังฟังก์ชันจบสิ้นการทำงาน
แบบนี้น่าจะเกิดข้อผิดพลาดขึ้นหรือไม่?
แต่ว่าผลก็คือ ฟังก์ชันสามารถเรียกใช้ได้ตามปกติ
alert(f2(2.3)); // ได้ 23
จากผลตรงนี้บอกให้เรารู้ว่า
หากตัวแปรด้านในฟังก์ชันถูกเรียกใช้โดยฟังก์ชันที่อยู่ด้านในฟังก์ชันด้วยกันเอง
ถ้าฟังก์ชันด้านในนั้นถูกส่งคืนออกมาด้านนอก ตัวแปรนั้นจะถูกบันทึกไว้ด้วย
และเมื่อฟังก์ชันด้านในนั้นถูกเอามาเรียกใช้ แม้จะเป็นการเรียกใช้ด้านนอกก็ตาม
ก็สามารถใช้ค่าตัวแปรนั้นได้
นี่คือคุณสมบัติของโปรแกรมที่เรียกว่า
"โคลเฌอร์" (closure)
อาจจะเข้าใจยากสักหน่อย แตก็เป็นเรื่องที่สำคัญเรื่องหนึ่ง
ประโยชน์อย่างหนึ่งของโคลเฌอร์ก็คือการสร้างสิ่งที่เรียกว่า
"พรอเพอร์ตีส่วนตัว" ให้ออบเจ็กต์ได้
พรอเพอร์ตีส่วนตัวและพรอเพอร์ตีสาธารณะ
ปกติแล้วพรอเพอร์ตีของออบเจ็กต์ในจาวาสคริปต์นั้น
เมื่อสร้างขึ้นมาแล้วสามารถเข้าถึงได้ทั้งหมด
ไม่ว่าจะจากภายในเมธอดของออบเจ็กต์นั้น
หรือเรียกโดยตรงจากนอกนอกออบเจ็กต์โดยพิมพ์ชื่อพรอเพอร์ตี แบบนี้เรียกว่า
พรอเพอร์ตีสาธารณะ (public property)
เช่น ลองสร้างคลาสของ "ผู้กล้า " (Phukla) ให้มีพรอเพอร์ตีคือ
"ชื่อ" (chue) แล้วให้มีเมธอด "บอกชื่อ" (bokchue)
สำหรับบอกชื่อ
var Phukla = function(chue) {
this.chue = chue;
};
Phukla.prototype.bokchue = function() {
alert("ฉันชื่อ" + this.chue);
};
var naofumi = new Phukla("นาโอฟุมิ");
alert(naofumi.chue); // ได้ นาโอฟุมิ
naofumi.bokchue(); // ได้ ฉันชื่อนาโอฟุมิ
(
อิวาตานิ นาโอฟุมิ)
ในตัวอย่างนี้จะเห็นว่าพรอเพอร์ตี chue ถูกเรียกผ่าน ๒ วิธีได้ทั้งคู่
คือเรียกผ่านเมธอด โดยเมธอด bokchue จะไปดึงข้อมูลชื่อมาให้
กับพิมพ์ชื่อพรอเพอร์ตี .chue เพื่อเข้าไปเอาค่าโดยตรง
นั่นเพราะพรอเพอร์ตีทั่วไปเป็นพรอเพอร์ตีสาธารณะ
แต่ว่าหากทำให้พรอเพอร์ตี chue เป็น
พรอเพอร์ตีส่วนตัว (private property)
นั่นหมายความว่าจะต้องเข้าถึงผ่านเมธอดเท่านั้น เข้าถึงโดยตรงจากข้างนอกไม่ได้
การสร้างพรอเพอร์ตีส่วนตัว
ในบางภาษามีวิธีง่ายๆในการสร้างพรอเพอร์ตีส่วนตัว แต่บางภาษาก็ไม่มี
ส่วนในจาวาสคริปต์นั้นจริงๆแล้วไม่มีพรอเพอร์ตีส่วนตัวจริงๆ
แต่สามารถสร้างสิ่งที่คล้ายกัน เสมือนว่าเป็นพรอเพอร์ตีส่วนตัวได้โดยใช้โคลเฌอร์
สำหรับวิธีการสร้างนั้นขออธิบายด้วยการยกตัวอย่าง เช่น สร้างคลาส
"ผู้กล้า" โดยให้ชื่อเป็นพรอเพอร์ตีส่วนตัว
var Phukla = function(chue) {
this.bokchue = function() {
alert("ฉันชื่อ" + chue);
};
};
var yuuna = new Phukla("ยูกิ ยูนะ");
yuuna.bokchue(); // ได้ ฉันชื่อยูกิ ยูนะ
alert(yuuna.chue); // ได้ undefined
(
ยูกิ ยูนะ)
คราวนี้ตัวแปร chue ที่ป้อนเข้าไปนั้นไม่ได้ถูกบันทึกไว้ในคอนสตรักเตอร์
แค่มีการเรียกจากฟังก์ชัน ซึ่งกลายเป็นเมธอด bokchue
ดังนั้นจึงไม่สามารถเข้าถึง chue ผ่านทางอื่นนอกจากผ่านเมธอด bokchue
เมธอดที่จะเข้าถึง chue
ได้จะต้องเป็นเมธอดที่ถูกสร้างขึ้นมาในคอนสตรักเตอร์และกำหนดไว้แต่แรกว่าให้เรียกใช้
chue เท่านั้น
ต่อให้สร้างเมธอดขึ้นมาทีหลังก็ย่อมไม่สามารถเข้าถึง chue ได้
เพราะไม่มีความเชื่อมโยงใดๆที่จะโยงไปถึง
เพราะหากไม่ได้ถูกใช้ในฟังก์ชันใดๆก็จะถูกลบไปตั้งแต่หลังสร้างอินสแตนซ์เสร็จแล้ว
var Phukla = function(chue) {};
var sumi = new Phukla("วาชิโอะ สึมิ");
sumi.bokchue = function() {
alert("ฉันชื่อ" + chue);
};
sumi.bokchue(); // ได้ ReferenceError: chue is not defined
ตรงจุดนี้ทำให้พรอเพอร์ตีส่วนตัวในจาวาสคริปต์นั้นแตกต่างจากในบางภาษาเช่นภาษาซีหรือรูบี
เพราะในซีและรูบีนั้น พรอเพอร์ตีส่วนตัวจะเข้าผ่านเมธอดใดๆก็ได้ในออบเจ็กต์นั้น
แต่พรอเพอร์ตีส่วนตัวในจาวาสคริปต์นั้นจะเข้าได้ผ่านเมธอดที่กำหนดไว้เท่านั้น
เรียกได้ว่าเป็นเมธอดที่ได้สิทธิพิเศษ
ถ้าเราไม่ได้เตรียมเมธอดสำหรับแก้ค่าไว้
ค่านั้นก็จะกลายเป็นค่าที่ไม่มีทางแก้ได้ไปด้วย
ถ้าเตรียมเมธอดสำหรับแก้ค่าเอาไว้ แม้แต่แก้ค่าก็สามารถทำได้ด้วย
ตัวอย่างเช่น ลองเขียนใหม่คล้ายเดิม คือเป็นคลาสผู้กล้าที่มีชื่อ
แต่คราวนี้เพิ่มเมธอดสำหรับตั้งชื่อไว้ด้วย
var Phukla = function() {
// ประกาศตัวแปรสำหรับเก็บชื่อ
var chue;
// เมธอดตั้งชื่อ
this.tangchue = function(x) {
chue = x;
};
// เมธอดบอกชื่อ
this.bokchue = function() {
alert("ฉันชื่อ" + chue);
};
};
var yuusha = new Phukla();
yuusha.bokchue(); // ได้ ฉันชื่อundefined
yuusha.tangchue("ยูเลีย ชาร์เด็ต");
yuusha.bokchue(); // ได้ ฉันชื่อยูเลีย ชาร์เด็ต
yuusha.tangchue("ยูชา");
yuusha.bokchue(); // ได้ ฉันชื่อยูชา
alert(yuusha.chue); // ได้ undefined
(ยูเลีย ชาร์เด็ต (ชื่อเล่น: ยูชา)
ที่มา)
ในตัวอย่างนี้ใช้เมธอด tangchue สำหรับตั้งชื่อ และเมธอด bokchue
สำหรับบอกชื่อ
จะเห็นว่าทั้ง ๒ เมธอดสามารถเข้าถึงตัวแปร chue ได้ โดยเมธอด tangchue
จะแก้ค่าตัวแปรนี้ได้ ส่วน bokchue จะให้ค่าตัวแปรนี้ออกมา
แต่นอกจากใช้ ๒ เมธอดนี้แล้ว ไม่มีวิธีอื่นที่จะเข้าถึงตัวแปร chue ในนั้นได้
เมธอดทั้ง ๒ นี้จึงเป็นเมธอดที่มีสิทธิพิเศษ
ด้วยวิธีการใช้หลักการของโคลเฌอร์แบบนี้
ทำให้จาวาสคริปต์สามารถมีพรอเพอร์ตีส่วนตัวได้
ส่งท้ายบท
บทนี้เป็นบทสุดท้ายในเนื้อหาส่วนพื้นฐาน ES3
ต่อจากนี้ไปจะเข้าสู่เนื้อหาที่เพิ่มเข้ามาใน ES5
ขอทิ้งท้ายด้วยคำพูดดีๆจาก ฮาบุ โยชิฮารุ (羽生 善治) นักเล่นหมากรุกญี่ปุ่น
「誰でも最初は真似から始める。しかし、丸暗記しようとするのではなく、どうしてその人がその航路をたどったのか、どういう過程でそこにたどり着いたのか、その過程を理解することが大切」
"ไม่ว่าใครตอนแรกก็เริ่มจากการเลียนแบบก่อน แต่ไม่ใช่ให้ท่องจำทั้งหมด
ที่สำคัญคือให้ทำความเข้าใจกระบวนการว่าทำไมคนนั้นเขาถึงเลือกทางนั้น
ทำยังไงจึงจะไปให้ถึงตรงนั้นได้"
(
ที่มา)