ในบทความก่อนหน้าแนวคิดของต้นแบบได้รับการแนะนำและความสัมพันธ์ระหว่างเพื่อนที่ดีทั้งสามของคอนสตรัคเตอร์วัตถุต้นแบบและอินสแตนซ์ในจาวาสคริปต์: แต่ละคอนสตรัคเตอร์มี "ผู้พิทักษ์" - วัตถุต้นแบบและยังมี "ตำแหน่ง" ของตัวสร้างในหัวใจของวัตถุต้นแบบ ทั้งสองมีความรัก แต่ตัวอย่างคือ "แอบรัก" กับวัตถุต้นแบบและเธอยังรักษาตำแหน่งของวัตถุต้นแบบในหัวใจของเธอ
JavaScript เองไม่ใช่ภาษาที่มุ่งเน้นวัตถุ แต่เป็นภาษาที่ใช้วัตถุ สำหรับผู้ที่คุ้นเคยกับภาษา OO อื่น ๆ มันค่อนข้างอึดอัดในตอนแรกเพราะไม่มีแนวคิดของ "คลาส" ที่นี่หรือไม่มีความแตกต่างระหว่าง "คลาส" และ "อินสแตนซ์" ดังนั้นกองวัตถุเหล่านี้ในจาวาสคริปต์จะเชื่อมโยงด้วยวิธีนี้อย่างไร
โชคดีที่ JavaScript ได้จัดทำวิธีการใช้งาน "มรดก" ในตอนต้นของการออกแบบ ก่อนที่จะเข้าใจ "มรดก" ตอนนี้เราจะเข้าใจแนวคิดของห่วงโซ่ต้นแบบ
ห่วงโซ่ต้นแบบ
เรารู้ว่าต้นแบบมีตัวชี้ไปยังตัวสร้าง ถ้าเราสร้างวัตถุต้นแบบ subclass เท่ากับอินสแตนซ์อื่นของ superclass ใหม่ () ในเวลานี้วัตถุต้นแบบ subclass มีตัวชี้ไปยังต้นแบบ superclass และต้นแบบ superclass ยังมีตัวชี้ไปยังตัวสร้างซูเปอร์คลาส - - ด้วยวิธีนี้ห่วงโซ่ต้นแบบจะเกิดขึ้น
รหัสเฉพาะมีดังนี้:
ฟังก์ชั่น superclass () {this.name = "ผู้หญิง"} superclass.prototype.say what = function () {return this.name + ": i`ma girl!"; } ฟังก์ชั่น subclass () {this.subname = "น้องสาวของคุณ"; } subclass.prototype = new superclass (); subclass.prototype.subsay what = function () {return this.subname + ": i`ma สาวสวย"; } var sub = subclass ใหม่ (); console.log (sub.say what ()); // ผู้หญิง: i`ma girl!ใช้ห่วงโซ่ต้นแบบเพื่อให้ได้มรดก
จากรหัสข้างต้นเราจะเห็นว่าคลาสย่อยสืบทอดคุณสมบัติและวิธีการของซูเปอร์คลาส การใช้งานที่สืบทอดมานี้คือการกำหนดอินสแตนซ์ของ superclass ให้กับวัตถุต้นแบบของคลาสย่อย ด้วยวิธีนี้วัตถุต้นแบบของคลาสย่อยจะถูกเขียนทับโดยอินสแตนซ์ของ superclass มีคุณสมบัติและวิธีการทั้งหมดและยังมีตัวชี้ไปยังวัตถุต้นแบบของ superclass
มีบางสิ่งที่ต้องให้ความสนใจเมื่อใช้การสืบทอดโดยใช้โซ่ต้นแบบ:
ให้ความสนใจกับการเปลี่ยนแปลงในตัวสร้างหลังจากรับมรดก ที่นี่ตัวสร้างของชี้ไปที่ superclass เนื่องจากต้นแบบของคะแนนย่อยเป็น superclass เมื่อทำความเข้าใจกับห่วงโซ่ต้นแบบอย่าเพิกเฉยต่อวัตถุวัตถุเริ่มต้นในตอนท้ายซึ่งเป็นสาเหตุที่เราสามารถใช้วิธีการในตัวเช่น toString ในวัตถุทั้งหมด
เมื่อใช้การสืบทอดผ่านห่วงโซ่ต้นแบบคุณไม่สามารถใช้คำจำกัดความที่แท้จริงของวิธีต้นแบบได้เนื่องจากสิ่งนี้จะเขียนวัตถุต้นแบบใหม่ (แนะนำในบทความก่อนหน้า):
ฟังก์ชั่น superclass () {this.name = "ผู้หญิง"} superclass.prototype.say what = function () {return this.name + ": i`ma girl!"; } ฟังก์ชั่น subclass () {this.subname = "น้องสาวของคุณ"; } subclass.prototype = new superclass (); subclass.prototype = {// วัตถุต้นแบบถูกเขียนทับที่นี่เพราะแอตทริบิวต์ superclass และวิธีการไม่สามารถสืบทอด subsay subsay what: function () {return this.subname + ": i`ma สาวสวย"; }} var sub = subclass ใหม่ (); console.log (sub.say what ()); // typeerror: undefined ไม่ใช่ฟังก์ชันปัญหาเกี่ยวกับการแบ่งปันอินสแตนซ์ เมื่ออธิบายต้นแบบและตัวสร้างก่อนหน้านี้เราเคยแนะนำว่าต้นแบบที่มีแอตทริบิวต์ประเภทอ้างอิงจะถูกแชร์โดยทุกกรณี ในทำนองเดียวกันคุณสมบัติของประเภทการอ้างอิงในต้นแบบ "คลาสแม่" จะถูกแชร์ในต้นแบบ เมื่อเราแก้ไขแอตทริบิวต์ประเภทการอ้างอิงของ "คลาสแม่" ผ่านการสืบทอดต้นแบบการสืบทอดอินสแตนซ์อื่น ๆ ทั้งหมดที่สืบทอดมาจากต้นแบบจะได้รับผลกระทบ สิ่งนี้ไม่เพียงสูญเสียทรัพยากรเท่านั้น แต่ยังเป็นปรากฏการณ์ที่เราไม่ต้องการเห็น:
ฟังก์ชั่น superclass () {this.name = "ผู้หญิง"; this.bra = ["a", "b"]; } ฟังก์ชั่น subclass () {this.subname = "น้องสาวของคุณ"; } subclass.prototype = new superclass (); var sub1 = subclass ใหม่ (); sub1.name = "man"; sub1.bra.push ("C"); console.log (sub1.name); // man console.log (sub1.bra); // ["a", "b", "c"] var sub2 = subclass ใหม่ (); console.log (sub1.name); // woman console.log (sub2.bra); // ["a", "b", "c"]หมายเหตุ: เพิ่มองค์ประกอบลงในอาร์เรย์ที่นี่อินสแตนซ์ทั้งหมดที่สืบทอดมาจาก SuperClass จะได้รับผลกระทบ แต่ถ้าคุณแก้ไขแอตทริบิวต์ชื่อมันจะไม่ส่งผลกระทบต่ออินสแตนซ์อื่นเนื่องจากอาร์เรย์เป็นประเภทอ้างอิงและชื่อเป็นประเภทดั้งเดิม
จะแก้ปัญหาการแบ่งปันอินสแตนซ์ได้อย่างไร? มาดูกันเถอะ ...
มรดกคลาสสิก (การขโมยคอนสตรัคเตอร์)
เนื่องจากเราได้แนะนำว่าเราไม่ค่อยใช้ต้นแบบเพื่อกำหนดวัตถุเพียงอย่างเดียวในการพัฒนาจริงเราไม่ค่อยใช้โซ่ต้นแบบเพียงอย่างเดียว เพื่อแก้ปัญหาประเภทการอ้างอิงผู้พัฒนา JavaScript ได้แนะนำรูปแบบการสืบทอดแบบคลาสสิก (บางคนเรียกว่ามรดกที่ยืมมาสร้าง) การใช้งานนั้นง่ายมากที่จะเรียกตัวสร้าง Supertype ในตัวสร้างชนิดย่อย เราจำเป็นต้องใช้ฟังก์ชั่นการโทร () หรือใช้ () ที่จัดทำโดย JavaScript มาดูตัวอย่าง:
ฟังก์ชั่น superclass () {this.name = "ผู้หญิง"; this.bra = ["a", "b"];} ฟังก์ชั่น subclass () {this.subname = "น้องสาวของคุณ"; // กำหนดขอบเขตของ superclass ให้กับตัวสร้างปัจจุบันเพื่อใช้การสืบทอดของ superclass.call (this);} var sub1 = subclass ใหม่ (); sub1.bra.push ("c"); console.log (sub1.bra); subclass (); console.log (sub2.bra); // ["a", "b"]superclass.call (นี่); ประโยคนี้หมายความว่าการเริ่มต้นงานของตัวสร้างซูเปอร์คลาสถูกเรียกในสภาพแวดล้อมอินสแตนซ์ (บริบท) ของคลาสย่อยเพื่อให้แต่ละอินสแตนซ์จะมีสำเนาของคุณลักษณะ BRA ของตัวเองซึ่งจะไม่มีผลต่อกันและกัน
อย่างไรก็ตามวิธีการใช้งานนี้ยังไม่สมบูรณ์แบบ เนื่องจากตัวสร้างได้รับการแนะนำเรายังเผชิญกับปัญหากับตัวสร้างที่กล่าวถึงในบทความก่อนหน้า: หากมีคำจำกัดความของวิธีการในตัวสร้างดังนั้นจึงมีการอ้างอิงฟังก์ชั่นแยกต่างหากสำหรับไม่มีอินสแตนซ์ จุดประสงค์ของเราคือการแบ่งปันวิธีนี้และวิธีการที่เรากำหนดในต้นแบบ supertype ไม่สามารถเรียกได้ในอินสแตนซ์ชนิดย่อย:
ฟังก์ชั่น superclass () {this.name = "ผู้หญิง"; this.bra = ["a", "b"]; } superclass.prototype.say what = function () {console.log ("hello"); } ฟังก์ชั่น subclass () {this.subname = "น้องสาวของคุณ"; superclass.call (นี่); } var sub1 = subclass ใหม่ (); console.log (sub1.say what ()); // typeerror: undefined ไม่ใช่ฟังก์ชันหากคุณได้อ่านบทความก่อนหน้านี้เกี่ยวกับวัตถุต้นแบบและตัวสร้างคุณต้องรู้คำตอบในการแก้ปัญหานี้อยู่แล้วนั่นคือทำตามกิจวัตรของบทความก่อนหน้านี้และใช้ "การรวมกันแบบผสมผสาน"!
มรดกแบบผสมผสาน
การสืบทอดการรวมกันเป็นวิธีการรวมข้อดีของห่วงโซ่ต้นแบบและตัวสร้างและเพื่อรวมเข้าด้วยกันเพื่อให้ได้มรดก พูดง่ายๆคือการใช้ห่วงโซ่ต้นแบบเพื่อสืบทอดคุณลักษณะและวิธีการและใช้ตัวสร้างที่ยืมมาเพื่อใช้การสืบทอดของแอตทริบิวต์อินสแตนซ์ สิ่งนี้ไม่เพียง แต่แก้ปัญหาของการแชร์แอตทริบิวต์อินสแตนซ์เท่านั้น แต่ยังช่วยให้แอตทริบิวต์และวิธีการที่ได้รับการสืบทอด
ฟังก์ชั่น superclass () {this.name = "ผู้หญิง"; this.bra = ["a", "b"]; } superclass.prototype.say what = function () {console.log ("hello"); } ฟังก์ชั่น subclass () {this.subname = "น้องสาวของคุณ"; superclass.call (นี่); // การโทรครั้งที่สองไปยัง SuperClass} subclass.prototype = new SuperClass (); // การโทรครั้งแรกไปยัง superclass var sub1 = subclass ใหม่ (); console.log (sub1.say what ()); //สวัสดีวิธีการสืบทอดการรวมกันเป็นวิธีที่ใช้กันมากที่สุดในการใช้การสืบทอดในการพัฒนาจริง ณ จุดนี้มันสามารถตอบสนองความต้องการในการพัฒนาที่แท้จริงของคุณ แต่การแสวงหาความสมบูรณ์แบบของผู้คนนั้นไม่มีที่สิ้นสุดดังนั้นจะมีคน "ค้นหา" เกี่ยวกับรูปแบบนี้อย่างหลีกเลี่ยงไม่ได้: รูปแบบของคุณเรียกว่าตัวสร้างประเภทซูเปอร์สองครั้ง! สองครั้ง - - คุณทำหรือไม่? การขยายตัวนี้เป็นการสูญเสียประสิทธิภาพ 100 เท่าหรือไม่?
การพิสูจน์ที่ทรงพลังที่สุดคือการแก้ปัญหา แต่โชคดีที่นักพัฒนาได้พบวิธีแก้ปัญหาที่ดีที่สุดสำหรับปัญหานี้:
การสืบทอดการรวมกันของกาฝาก
ก่อนที่จะแนะนำวิธีการสืบทอดนี้ก่อนอื่นเราเข้าใจแนวคิดของตัวสร้างกาฝาก ตัวสร้างกาฝากคล้ายกับรูปแบบโรงงานที่กล่าวถึงข้างต้น ความคิดของมันคือการกำหนดฟังก์ชั่นทั่วไป ฟังก์ชั่นนี้ใช้โดยเฉพาะเพื่อจัดการกับการสร้างวัตถุ หลังจากการสร้างเสร็จสมบูรณ์จะส่งคืนวัตถุนี้ ฟังก์ชั่นนี้คล้ายกับตัวสร้าง แต่ตัวสร้างไม่คืนค่า:
ฟังก์ชั่น gf (ชื่อ, bra) {var obj = วัตถุใหม่ (); obj.name = ชื่อ; obj.bra = bra; obj.say what = function () {console.log (this.name); } return obj;} var gf1 = new gf ("bingbing", "c ++"); console.log (gf1.say what ()); // bingbingการดำเนินการตามมรดกของปรสิตนั้นคล้ายกับตัวสร้างกาฝาก มันสร้างฟังก์ชั่น "โรงงาน" ที่ไม่ได้ขึ้นอยู่กับประเภทเฉพาะเกี่ยวข้องกับกระบวนการสืบทอดวัตถุโดยเฉพาะจากนั้นส่งคืนอินสแตนซ์วัตถุที่สืบทอดมา โชคดีที่สิ่งนี้ไม่ต้องการให้เราใช้มันเอง Dao GE (Douglas) ได้ให้วิธีการดำเนินงานแก่เรามานานแล้ว:
วัตถุฟังก์ชัน (obj) {ฟังก์ชั่น f () {} f.prototype = obj; ส่งคืน f f ();} var superclass = {ชื่อ: "bingbing", bra: "c ++"} var subclass = object (superclass); console.log (subclass.name); // bingbingตัวสร้างที่เรียบง่ายมีให้ในฟังก์ชั่นสาธารณะและจากนั้นอินสแตนซ์ของวัตถุที่ส่งผ่านถูกกำหนดให้กับวัตถุต้นแบบของตัวสร้างและในที่สุดก็กลับอินสแตนซ์ของตัวสร้างนั้นง่ายมาก แต่ประสิทธิภาพนั้นดีมากใช่ไหม? วิธีนี้เรียกว่า "การสืบทอดต้นแบบ" และการสืบทอดปรสิตจะเกิดขึ้นได้จากต้นแบบโดยการเพิ่มคุณสมบัติที่กำหนดเองของวัตถุ:
ฟังก์ชั่น buildobj (obj) {var o = วัตถุ (obj); o.say what = function () {console.log ("hello"); } return o;} var superclass = {ชื่อ: "bingbing", bra: "c ++"} var gf = buildobj (superclass); gf.say what (); // สวัสดีการสืบทอดของกาฝากยังเผชิญกับปัญหาของการใช้งานซ้ำในต้นแบบดังนั้นผู้คนจึงเริ่มรวบรวมการสร้างบล็อกอีกครั้งและการสืบทอดการรวมกันของกาฝากก็เกิดขึ้นโดยมีจุดประสงค์ในการแก้ปัญหาการเรียกตัวสร้างประเภทหลักเมื่อระบุต้นแบบย่อยและในเวลาเดียวกัน ขึ้นอยู่กับวิธีการใช้งานพื้นฐานข้างต้นมีดังนี้:
// พารามิเตอร์เป็นฟังก์ชั่นตัวสร้างสองตัวที่สืบทอดมาจาก (ย่อย, sup) {// ใช้การสืบทอดอินสแตนซ์และรับสำเนาของ supertype var proto = object (sup.prototype); // respecify แอตทริบิวต์ตัวสร้างของ proto instance proto.constructor = sub; // กำหนดวัตถุที่สร้างขึ้นให้กับต้นแบบของ subtype sub.prototype = proto;} function superclass () {this.name = "ผู้หญิง"; this.bra = ["a", "b"];} superclass.prototype.say what = function () {console.log ("hello");} ฟังก์ชั่น subclass () {this.name = "ผู้หญิง"; this.bra = ["a", "b"];} superclass.prototype.say what = function () {console.log ("hello");} ฟังก์ชั่น subclass () {this.subname = "น้องสาวของคุณ"; superclass.call (this);} mandleitobj (subclass, superclass); var sub1 = subclass ใหม่ (); console.log (sub1.say what ()); //สวัสดีการใช้งานนี้จะหลีกเลี่ยงการโทรสองครั้งไปยัง supertypes และยังช่วยประหยัดคุณสมบัติที่ไม่จำเป็นใน subclass.prototype และยังคงรักษาห่วงโซ่ต้นแบบ ณ จุดนี้การเดินทางมรดกสิ้นสุดลงอย่างแท้จริงและการดำเนินการนี้ก็กลายเป็นวิธีการใช้งานมรดกที่เหมาะที่สุด! การโต้เถียงของผู้คนเกี่ยวกับมรดกของ JavaScript ยังคงดำเนินต่อไป ผู้สนับสนุนบางคนและบางคนคัดค้านความพยายามที่ไม่จำเป็นใน JavaScript เพื่อตระหนักถึงลักษณะของ OO ถ้าเป็นอย่างน้อยฉันก็มีความเข้าใจที่ลึกซึ้งยิ่งขึ้น!