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



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


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

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

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

สารบัญ

รวมคำแปลวลีเด็ดจากญี่ปุ่น
มอดูลต่างๆ
-- numpy
-- matplotlib

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

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



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

  ค้นหาบทความ

  บทความแนะนำ

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

บทความแต่ละเดือน

2024年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2023年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2022年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2021年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

2020年

1月 2月 3月 4月
5月 6月 7月 8月
9月 10月 11月 12月

ค้นบทความเก่ากว่านั้น

ไทย

日本語

中文