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



javascript เบื้องต้น บทที่ ๒๑: โพรโทไทป์
เขียนเมื่อ 2019/07/31 23:33


โพรโทไทป์

หากคอนสตรักเตอร์เปรียบเสมือนแม่พิมพ์กุญแจ อินสแตนซ์ก็เปรียบเหมือนกุญแจที่สร้างจากแม่พิมพ์นี้

แต่ว่าปกติการจะสร้างแม่พิมพ์นั้นขึ้นมาได้ จะต้องมีตัวต้นแบบก่อน แม่พิมพ์กุญแจต้องถูกสร้างขึ้นมาจากกุญแจที่เป็นต้นแบบ

ตัวต้นแบบนั้นเรียกว่า "โพรโทไทป์" (prototype)

ในตอนที่เรานิยามคอนสตรักเตอร์ขึ้นมานั้น จริงๆแล้วเป็นการสร้างโพรโทไทป์ขึ้น แล้วเราก็ได้คอนสตรักเตอร์ตามมาด้วยพร้อมกัน

โพรโทไทป์ของคอนสตรักเตอร์นั้นสามารถเข้าถึงได้โดยผ่านพรอเพอร์ตี prototype

ลองมาดูกัน
function Cons() {}

var proto = Cons.prototype;
alert(proto); // ได้ [object Object]
alert(proto.constructor); // ได้ function Cons() {}
alert(proto instanceof Cons); // ได้ false
alert(new proto.constructor() instanceof Cons); // ได้ true

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

โพรโทไทป์ก็เป็นออบเจ็กต์เหมือนกับอินสแตนซ์ แต่ไม่ใช่อินสแตนซ์ มีฐานะที่พิเศษกว่านั้น

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

แต่ว่าในทางกลับกัน พรอเพอร์ตีที่ใส่ให้กับอินสแตนซ์จะไม่ปรากฏในโพรโทไทป์
function Cons() {
  this.prop1 = "อิจิ";
}

var proto = Cons.prototype;
proto.prop2 = "นิ";
var inst = new Cons();
proto.prop3 = "ซัง";
alert(inst.prop1); // ได้ อิจิ
alert(inst.prop2); // ได้ นิ
alert(inst.prop3); // ได้ ซัง

inst.prop4 = "ชิ";
alert(proto.prop1); // ได้ undefined
alert(proto.prop4); // ได้ undefined

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

ดังนั้นหากมีพรอเพอร์อะไรที่ต้องการให้ทุกอินสแตนซ์มีเหมือนกันหมดก็ให้กำหนดที่ตัวโพรโทไทป์



เมื่อพรอเพอร์ตีในอินสแตนซ์ไปซ้ำกับในโพรโทไทป์

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

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

ตัวอย่าง
function Cons() {}

var proto = Cons.prototype;
proto.prop = "เกาลัด";
var inst1 = new Cons();
var inst2 = new Cons();
inst1.prop = "ลูกพลับ";

alert(proto.prop); // ได้ เกาลัด
alert(inst1.prop); // ได้ ลูกพลับ
alert(inst2.prop); // ได้ เกาลัด

proto.prop = "เห็ด";

alert(proto.prop); // ได้ เห็ด
alert(inst1.prop); // ได้ ลูกพลับ
alert(inst2.prop); // ได้ เห็ด

ในที่นี้ .prop ถูกป้อนให้กับ inst1 ดังนั้น .prop ของ inst1 ก็จะเป็น .prop ตัวนี้ ไม่เกี่ยวอะไรกับ .prop ในโพรโทไทป์อีก

แต่ inst2 ไม่มีการป้อน .prop ใหม่ให้ ดังนั้นจึงเป็นค่าเดียวกับในโพรโทไทป์ เปลี่ยนตามกันไปตลอด

เพียงแต่หากลบพรอเพอร์ตีตัวนั้นทิ้งไปจากอินสแตนซ์ พรอเพอร์ตีตัวนั้นก็จะกลับมาเป็นค่าในโพรโทไทป์ได้อีก
delete inst1.prop;
alert(inst1.prop); // ได้ เห็ด

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



การตรจสอบว่าเป็นพรอเพอร์ตีในตัวอินแสตนซ์เองหรือในโพรโทไทป์

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

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

ตัวอย่าง
function Constr() {
  this.propa = 11; // พรอเพอร์ตีตั้งต้นใส่ในตัวอินแสตนซ์
}
Constr.prototype.propi = 33; // สร้าง propi เป็นพรอเพอร์ตีในโพรโทป์

var insta = new Constr();
insta.prope = 22; // พรอเพอร์ตีเพิ่มเติมเฉพาะของ insta
alert(insta.hasOwnProperty("propa")); // ได้ true
alert(insta.hasOwnProperty("prope")); // ได้ true
alert(insta.hasOwnProperty("propi")); // ได้ false
insta.propi = 44; // ทับ propi กลายเป็นพรอเพอร์ตีในตัว insta
alert(insta.hasOwnProperty("propi")); // ได้ true
delete insta.propi; // ลบ propi ในตัว insta ทำให้ propi กลับไปป็นพรอเพอร์ตีในโพรโทไทป์
alert(insta.hasOwnProperty("propi")); // ได้ false
alert(insta.hasOwnProperty("hasOwnProperty")); // ได้ false


สร้างเมธอดให้โพรโทไทป์

ด้วยความที่พรอเพอร์ตีที่ป้อนให้โพรโทไทป์จะไปปรากฏในอินสแตนซ์ทั้งหมด ดังนั้นหากต้องการสร้างเมธอดจึงมักกำหนดที่ตัวโพรโทไทป์

หากลองสร้างคอนสตรักเตอร์ Phanakngan เช่นเดียวกับในตัวอย่างที่ยกไปในบทที่แล้ว แต่เปลี่ยนเป็นกำหนดเมธอดลงในโพรโทไทป์จะเขียนได้แบบนี้
function Phanakngan(chue, namsakun) {
  this.chue = chue;
  this.namsakun = namsakun;
}

Phanakngan.prototype.naenamtua = function() {
  alert("ฉันชื่อ" + this.namsakun + " " + this.chue);
};

var hajime = new Phanakngan("ฮาจิเมะ", "ชิโนดะ");
hajime.naenamtua(); // ได้ ฉันชื่อชิโนดะ ฮาจิเมะ
(ชิโนดะ ฮาจิเมะ)

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

นอกจากนี้ โดยทั่วไปหากมีเมธอดอยู่หลายตัว มักจะเขียนในรูปแบบการสร้างออบเจ็กต์ใหม่ใส่ทับลงไปในโพรโทไทป์ คือเขียนแบบนี้
function Phanakngan(chue, namsakun) {
  this.chue = chue;
  this.namsakun = namsakun;
}

Phanakngan.prototype = {
  // เมธอดแนะนำตัว
  naenamtua: function() {
    alert("ฉันชื่อ" + this.namsakun + " " + this.chue);
  },
  // เมธอดทักทาย
  thakthai: function(x) {
    alert("สวัสดี คุณ" + x.namsakun);
  }
};

var umiko = new Phanakngan("อุมิโกะ", "อาฮางง");
var shizuku = new Phanakngan("ชิซึกุ", "ฮาซึกิ");
umiko.naenamtua(); // ได้ ฉันชื่ออาฮางง อุมิโกะ
umiko.thakthai(shizuku); // ได้ สวัสดี คุณฮาซึกิ
(อาฮางง กับ ฮาซึกิ)

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



การใส่เมธอดให้โพรโทไทป์ของข้อมูลชนิดที่มีอยู่แล้ว

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

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

สามารถแก้โพรโทไทป์ได้โดยผ่านพรอเพอร์ตี .prototype ของคอนสตรักเตอร์

ตัวอย่างเช่นต้องการเพิ่มเมธอดให้ตัวเลขทุกตัว ก็ใส่เมธอดลงไปที่ Number.prototype
Number.prototype.pow = function(x) {
  return Math.pow(this, x);
};
alert((2).pow(4)); // ได้ 16

หรือสามารถลองใส่เมธอดสำหรับเรียงกลับด้านอักษรจากหลังมาหน้า
String.prototype.riangklaplang = function() {
  return this.split("").reverse().join("");
};
alert("กอด".riangklaplang()); // ได้ ดอก

หากใส่เมธอดให้กับออบเจ็กต์ จะสามารถเรียกใช้ได้จากข้อมูลทุกชนิดแม้แต่ข้อมูลชนิดตัวเลข สายอักขระ และบูลด้วย เพราะทุกสิ่งทุกอย่างถือเป็นออบเจ็กต์
Object.prototype.alertInstance = function() {
  alert(this + " เป็นอินสแตนซ์ของ " + /\S+\(\)/.exec(this.constructor));
};
true.alertInstance(); // ได้ true เป็นอินสแตนซ์ของ Boolean()
(1).alertInstance(); // ได้ 1 เป็นอินสแตนซ์ของ Number()
"xxx".alertInstance(); // ได้ xxx เป็นอินสแตนซ์ของ String()
({}.alertInstance()); // ได้ [object Object] เป็นอินสแตนซ์ของ Object()
[1, 2, 3].alertInstance(); // ได้ 1,2,3 เป็นอินสแตนซ์ของ Array()
new Date("2019-09-01").alertInstance(); // ได้ Sat Sep 01 2019 07:00:00 GMT+0700 เป็นอินสแตนซ์ของ Date()



เปรียบเทียบโพรโทไทป์กับคลาสในภาษาอื่น

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

โพรโทไทป์ในจาวาสคริปต์นั้น อาจเทียบได้กับสิ่งที่เรียกว่าคลาส ในภาษาอื่น

จาวาสคริปต์นั้นเป็นภาษาที่มีแนวคิดเชิงวัตถุแบบยืนพื้นบนโพรโทไทป์ (prototype-based) ต่างจากภาษาอื่นส่วนใหญ่ที่เป็นแบบยืนพื้นบนคลาส (class-based)

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

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

แต่สำหรับคลาสในภาษาโปรแกรมอื่นๆส่วนใหญ่ไม่ใช่แบบนั้น คลาสนั้นกำหนดขอบเขตข้อจำกัดให้กับออบเจ็กต์ในแต่ละคลาสมากกว่า

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

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

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

วิธีการนั้นจะไปเขียนถึงในส่วนที่อธิบายเนื้อหา ES6

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




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

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

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

หมวดหมู่

-- คอมพิวเตอร์ >> เขียนโปรแกรม >> 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
ตระเวนเที่ยวตามรอยฉากของอนิเมะในญี่ปุ่น
เที่ยวชมหอดูดาวที่ฐานสังเกตการณ์ซิงหลง
บันทึกการเที่ยวญี่ปุ่นครั้งแรกในชีวิต - ทุกอย่างเริ่มต้นที่สนามบินนานาชาติคันไซ
หลักการเขียนทับศัพท์ภาษาญี่ปุ่น
ทำไมจึงไม่ควรเขียนวรรณยุกต์เวลาทับศัพท์ภาษาต่างประเทศ
ทำไมถึงอยากมาเรียนต่อนอก
เหตุผลอะไรที่ต้องใช้ภาษาวิบัติ?

ไทย

日本語

中文