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



javascript เบื้องต้น บทที่ ๒๑: โพรโทไทป์
เขียนเมื่อ 2019/07/31 23:33
แก้ไขล่าสุด 2021/09/28 16:42


โพรโทไทป์

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

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

ตัวต้นแบบนั้นเรียกว่า "โพรโทไทป์" (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

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

สารบัญ

รวมคำแปลวลีเด็ดจากญี่ปุ่น
มอดูลต่างๆ
-- 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月

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

ไทย

日本語

中文