φυβλαςのβλογ
บล็อกของ phyblas



javascript เบื้องต้น บทที่ ๒๔: โคลเชอร์และการสร้างพรอเพอร์ตีส่วนตัวในออบเจ็กต์
เขียนเมื่อ 2019/08/02 17:42


ฟังก์ชันในฟังก์ชัน

ค่าที่มีการคืนกลับด้วยคำสั่ง 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

ขอทิ้งท้ายด้วยคำพูดดีๆจาก ฮาบุ โยชิฮารุ (羽生 善治) นักเล่นหมากรุกญี่ปุ่น

「誰でも最初は真似から始める。しかし、丸暗記しようとするのではなく、どうしてその人がその航路をたどったのか、どういう過程でそこにたどり着いたのか、その過程を理解することが大切」
"ไม่ว่าใครตอนแรกก็เริ่มจากการเลียนแบบก่อน แต่ไม่ใช่ให้ท่องจำทั้งหมด ที่สำคัญคือให้ทำความเข้าใจกระบวนการว่าทำไมคนนั้นเขาถึงเลือกทางนั้น ทำยังไงจึงจะไปให้ถึงตรงนั้นได้"

(ที่มา)




-----------------------------------------

囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧囧

ดูสถิติของหน้านี้

หมวดหมู่

-- คอมพิวเตอร์ >> เขียนโปรแกรม >> javascript

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

สารบัญ

รวมคำแปลวลีเด็ดจากญี่ปุ่น
python
-- numpy
-- matplotlib

-- pandas
-- pytorch
maya
การเรียนรู้ของเครื่อง
-- โครงข่าย
     ประสาทเทียม
javascript
บันทึกในญี่ปุ่น
บันทึกในจีน
-- บันทึกในปักกิ่ง
บันทึกในไต้หวัน
บันทึกในยุโรปเหนือ
บันทึกในประเทศอื่นๆ
เรียนภาษาจีน
qiita
บทความอื่นๆ

บทความแบ่งตามหมวด



ติดตามอัปเดตของบล็อกได้ที่แฟนเพจ

  ค้นหาบทความ

  บทความแนะนำ

เรียนรู้วิธีการใช้ regular expression (regex)
หลักการเขียนทับศัพท์ภาษาจีนกวางตุ้ง
การใช้ unix shell เบื้องต้น ใน linux และ mac
หลักการเขียนทับศัพท์ภาษาจีนกลาง
g ในภาษาญี่ปุ่นออกเสียง "ก" หรือ "ง" กันแน่
ทำความรู้จักกับปัญญาประดิษฐ์และการเรียนรู้ของเครื่อง
ค้นพบระบบดาวเคราะห์ ๘ ดวง เบื้องหลังความสำเร็จคือปัญญาประดิษฐ์ (AI)
หอดูดาวโบราณปักกิ่ง ตอนที่ ๑: แท่นสังเกตการณ์และสวนดอกไม้
พิพิธภัณฑ์สถาปัตยกรรมโบราณปักกิ่ง
เที่ยวเมืองตานตง ล่องเรือในน่านน้ำเกาหลีเหนือ
บันทึกการเที่ยวสวีเดน 1-12 พ.ค. 2014
แนะนำองค์การวิจัยและพัฒนาการสำรวจอวกาศญี่ปุ่น (JAXA)
เล่าประสบการณ์ค่ายอบรมวิชาการทางดาราศาสตร์โดยโซวเคนได 10 - 16 พ.ย. 2013
ตระเวนเที่ยวตามรอยฉากของอนิเมะในญี่ปุ่น
เที่ยวชมหอดูดาวที่ฐานสังเกตการณ์ซิงหลง
บันทึกการเที่ยวญี่ปุ่นครั้งแรกในชีวิต - ทุกอย่างเริ่มต้นที่สนามบินนานาชาติคันไซ
หลักการเขียนทับศัพท์ภาษาญี่ปุ่น
ทำไมจึงไม่ควรเขียนวรรณยุกต์เวลาทับศัพท์ภาษาต่างประเทศ
ทำไมถึงอยากมาเรียนต่อนอก
เหตุผลอะไรที่ต้องใช้ภาษาวิบัติ?

ไทย

日本語

中文