ในความหมายที่แท้จริง JavaScript ไม่ใช่ภาษาที่มุ่งเน้นวัตถุและไม่ได้ให้วิธีการสืบทอดแบบดั้งเดิม แต่เป็นวิธีการสืบทอดต้นแบบโดยใช้คุณสมบัติต้นแบบที่ให้เพื่อการสืบทอด
ต้นแบบและห่วงโซ่ต้นแบบ
ก่อนที่จะพูดถึงการสืบทอดต้นแบบเราควรพูดคุยเกี่ยวกับต้นแบบและโซ่ต้นแบบก่อน ท้ายที่สุดนี่เป็นพื้นฐานสำหรับการตระหนักถึงการสืบทอดต้นแบบ
ใน JavaScript แต่ละฟังก์ชั่นมีต้นแบบต้นแบบต้นแบบที่ชี้ไปที่ต้นแบบของตัวเองและวัตถุที่สร้างโดยฟังก์ชั่นนี้ยังมีแอตทริบิวต์ __Proto__ ที่ชี้ไปที่ต้นแบบนี้ ต้นแบบของฟังก์ชั่นเป็นวัตถุดังนั้นวัตถุนี้จะมี __proto__ ที่ชี้ไปที่ต้นแบบของตัวเองเพื่อให้ชั้นลึกลงไปโดยเลเยอร์จนกระทั่งต้นแบบของวัตถุวัตถุจึงสร้างห่วงโซ่ต้นแบบ รูปต่อไปนี้อธิบายความสัมพันธ์ระหว่างต้นแบบและห่วงโซ่ต้นแบบในจาวาสคริปต์ได้เป็นอย่างดี
แต่ละฟังก์ชั่นเป็นวัตถุที่สร้างขึ้นโดยฟังก์ชั่นฟังก์ชั่นดังนั้นแต่ละฟังก์ชั่นจึงมีแอตทริบิวต์ __proto__ ที่ชี้ไปที่ต้นแบบของฟังก์ชันฟังก์ชัน มันควรจะชี้ให้เห็นที่นี่ว่าสิ่งที่เป็นรูปแบบห่วงโซ่ต้นแบบคือแอตทริบิวต์ __Proto__ ของแต่ละวัตถุไม่ใช่แอตทริบิวต์ต้นแบบของฟังก์ชั่นซึ่งสำคัญมาก
มรดกต้นแบบ
โหมดพื้นฐาน
การคัดลอกรหัสมีดังนี้:
var parent = function () {
this.name = 'parent';
-
parent.prototype.getName = function () {
ส่งคืนสิ่งนี้ชื่อ;
-
parent.prototype.obj = {a: 1};
var child = function () {
this.name = 'ลูก';
-
child.prototype = ผู้ปกครองใหม่ ();
var parent = parent ใหม่ ();
var child = เด็กใหม่ ();
console.log (parent.getName ()); //พ่อแม่
console.log (child.getName ()); //เด็ก
นี่เป็นวิธีที่ง่ายที่สุดในการใช้งานการสืบทอดต้นแบบโดยตรงการกำหนดวัตถุของคลาสหลักให้กับต้นแบบของคอนสตรัคเตอร์คลาสย่อยเพื่อให้วัตถุของคลาสย่อยสามารถเข้าถึงคุณสมบัติในคลาสพาเรนต์และต้นแบบของตัวสร้างคลาสแม่ แผนภาพการสืบทอดต้นแบบของวิธีนี้มีดังนี้:
ข้อดีของวิธีนี้ชัดเจนการใช้งานนั้นง่ายมากและไม่จำเป็นต้องมีการดำเนินการพิเศษใด ๆ ข้อเสียก็ชัดเจนเช่นกัน หาก subclass จำเป็นต้องดำเนินการเริ่มต้นเช่นเดียวกับในตัวสร้างคลาสแม่คุณต้องทำซ้ำการดำเนินการในคลาสพาเรนต์ในคอนสตรัคเตอร์ subclass:
การคัดลอกรหัสมีดังนี้:
var parent = function (ชื่อ) {
this.name = name || 'พ่อแม่' ;
-
parent.prototype.getName = function () {
ส่งคืนสิ่งนี้ชื่อ;
-
parent.prototype.obj = {a: 1};
var child = function (ชื่อ) {
this.name = name || 'เด็ก' ;
-
child.prototype = ผู้ปกครองใหม่ ();
var parent = parent ใหม่ ('myparent');
var child = เด็กใหม่ ('mychild');
console.log (parent.getName ()); // myparent
console.log (child.getName ()); // mychild
ในสถานการณ์ข้างต้นจะต้องใช้แอตทริบิวต์ชื่อเท่านั้นที่จะเริ่มต้น หากการเริ่มต้นทำงานยังคงเพิ่มขึ้นวิธีนี้ไม่สะดวกมาก ดังนั้นจึงมีวิธีปรับปรุงสิ่งต่อไปนี้
ยืมตัวสร้าง
การคัดลอกรหัสมีดังนี้:
var parent = function (ชื่อ) {
this.name = name || 'พ่อแม่' ;
-
parent.prototype.getName = function () {
ส่งคืนสิ่งนี้ชื่อ;
-
parent.prototype.obj = {a: 1};
var child = function (ชื่อ) {
parent.apply (นี่, อาร์กิวเมนต์);
-
child.prototype = ผู้ปกครองใหม่ ();
var parent = parent ใหม่ ('myparent');
var child = เด็กใหม่ ('mychild');
console.log (parent.getName ()); // myparent
console.log (child.getName ()); // mychild
วิธีการข้างต้นดำเนินการเริ่มต้นงานเดียวกันโดยใช้การโทรไปยังคอนสตรัคเตอร์คลาสแม่ในคอนสตรัคเตอร์คลาสย่อยดังนั้นไม่ว่าการเริ่มต้นจะทำงานในคลาสแม่มากแค่ไหน อย่างไรก็ตามมีปัญหาอีกประการหนึ่งเกี่ยวกับการใช้งานข้างต้น ตัวสร้างคลาสแม่ถูกดำเนินการสองครั้งหนึ่งครั้งในคอนสตรัคเตอร์คลาสย่อยและอีกครั้งในต้นแบบย่อยคลาสนี่เป็นซ้ำซ้อนมากดังนั้นเราจึงต้องปรับปรุง:
การคัดลอกรหัสมีดังนี้:
var parent = function (ชื่อ) {
this.name = name || 'พ่อแม่' ;
-
parent.prototype.getName = function () {
ส่งคืนสิ่งนี้ชื่อ;
-
parent.prototype.obj = {a: 1};
var child = function (ชื่อ) {
parent.apply (นี่, อาร์กิวเมนต์);
-
child.prototype = parent.prototype;
var parent = parent ใหม่ ('myparent');
var child = เด็กใหม่ ('mychild');
console.log (parent.getName ()); // myparent
console.log (child.getName ()); // mychild
ด้วยวิธีนี้เราจะต้องดำเนินการคอนสตรัคเตอร์คลาสแม่เพียงครั้งเดียวในตัวสร้างคลาสย่อยและในเวลาเดียวกันเราสามารถสืบทอดคุณสมบัติในต้นแบบคลาสแม่ สิ่งนี้สอดคล้องกับความตั้งใจดั้งเดิมของต้นแบบซึ่งคือการวางเนื้อหาที่จำเป็นต้องนำกลับมาใช้ใหม่ในต้นแบบและเราได้รับเนื้อหาที่นำมาใช้ใหม่ได้ในต้นแบบเท่านั้น แผนภาพต้นแบบของวิธีการข้างต้นมีดังนี้:
โหมดตัวสร้างชั่วคราว (โหมดจอกศักดิ์สิทธิ์)
ยังคงมีปัญหากับเวอร์ชันที่ยืมรูปแบบตัวสร้างด้านบน มันกำหนดต้นแบบของคลาสแม่ให้กับต้นแบบของคลาสย่อยโดยตรงซึ่งจะทำให้เกิดปัญหานั่นคือถ้าต้นแบบของคลาสย่อยถูกแก้ไขการปรับเปลี่ยนจะส่งผลกระทบต่อต้นแบบของคลาสแม่แล้วส่งผลกระทบต่อวัตถุคลาสแม่ นี่ไม่ใช่สิ่งที่ทุกคนหวังว่าจะได้เห็น เพื่อแก้ปัญหานี้มีรูปแบบตัวสร้างชั่วคราว
การคัดลอกรหัสมีดังนี้:
var parent = function (ชื่อ) {
this.name = name || 'พ่อแม่' ;
-
parent.prototype.getName = function () {
ส่งคืนสิ่งนี้ชื่อ;
-
parent.prototype.obj = {a: 1};
var child = function (ชื่อ) {
parent.apply (นี่, อาร์กิวเมนต์);
-
var f = ฟังก์ชันใหม่ () {};
f.prototype = parent.prototype;
child.prototype = new f ();
var parent = parent ใหม่ ('myparent');
var child = เด็กใหม่ ('mychild');
console.log (parent.getName ()); // myparent
console.log (child.getName ()); // mychild
แผนภาพการสืบทอดต้นแบบของวิธีนี้มีดังนี้:
เป็นเรื่องง่ายที่จะเห็นว่าโดยการเพิ่มตัวสร้างชั่วคราว F ระหว่างต้นแบบคลาสแม่และต้นแบบระดับเด็กการเชื่อมต่อระหว่างต้นแบบคลาสเด็กและต้นแบบระดับพาเรนต์จะถูกตัดออกเพื่อให้ต้นแบบระดับพาเรนต์จะไม่ได้รับผลกระทบ
วิธีการของฉัน
โหมด Holy Grail สิ้นสุดใน "JavaScript Mode" แต่ไม่ว่าวิธีใดข้างต้นจะมีปัญหาที่ไม่ง่ายที่จะค้นพบ คุณจะเห็นว่าฉันเพิ่มแอตทริบิวต์ตัวอักษร OBJ Object ลงในคุณสมบัติต้นแบบของ 'Parent' แต่มันไม่เคยมีประโยชน์ มาดูสถานการณ์ต่อไปนี้ตามโหมด Holy Grail:
การคัดลอกรหัสมีดังนี้:
var parent = function (ชื่อ) {
this.name = name || 'พ่อแม่' ;
-
parent.prototype.getName = function () {
ส่งคืนสิ่งนี้ชื่อ;
-
parent.prototype.obj = {a: 1};
var child = function (ชื่อ) {
parent.apply (นี่, อาร์กิวเมนต์);
-
var f = ฟังก์ชันใหม่ () {};
f.prototype = parent.prototype;
child.prototype = new f ();
var parent = parent ใหม่ ('myparent');
var child = เด็กใหม่ ('mychild');
console.log (child.obj.a); // 1
console.log (parent.obj.a); // 1
child.obj.a = 2;
console.log (child.obj.a); // 2
console.log (parent.obj.a); // 2
ในกรณีข้างต้นเมื่อฉันแก้ไขวัตถุเด็ก obj.a, obj.a ในต้นแบบคลาสแม่จะถูกแก้ไขด้วยซึ่งจะทำให้เกิดปัญหาเช่นเดียวกับต้นแบบที่ใช้ร่วมกัน สิ่งนี้เกิดขึ้นเพราะเมื่อเข้าถึง child.obj.a เราจะติดตามห่วงโซ่ต้นแบบและค้นหาต้นแบบของคลาสแม่จากนั้นค้นหาแอตทริบิวต์ OBJ จากนั้นแก้ไข OBJ.A มาดูสถานการณ์ต่อไปนี้:
การคัดลอกรหัสมีดังนี้:
var parent = function (ชื่อ) {
this.name = name || 'พ่อแม่' ;
-
parent.prototype.getName = function () {
ส่งคืนสิ่งนี้ชื่อ;
-
parent.prototype.obj = {a: 1};
var child = function (ชื่อ) {
parent.apply (นี่, อาร์กิวเมนต์);
-
var f = ฟังก์ชันใหม่ () {};
f.prototype = parent.prototype;
child.prototype = new f ();
var parent = parent ใหม่ ('myparent');
var child = เด็กใหม่ ('mychild');
console.log (child.obj.a); // 1
console.log (parent.obj.a); // 1
child.obj.a = 2;
console.log (child.obj.a); // 2
console.log (parent.obj.a); // 2
มีปัญหาสำคัญที่นี่ เมื่อวัตถุเข้าถึงคุณสมบัติในต้นแบบคุณสมบัติในต้นแบบจะอ่านอย่างเดียวกับวัตถุ กล่าวคือวัตถุเด็กสามารถอ่านวัตถุ OBJ ได้ แต่การอ้างอิงวัตถุ OBJ ในต้นแบบไม่สามารถแก้ไขได้ ดังนั้นเมื่อเด็กดัดแปลง OBJ มันจะไม่ส่งผลกระทบต่อ OBJ ในต้นแบบ มันเพิ่มแอตทริบิวต์ OBJ ลงในวัตถุของตัวเองโดยเขียนทับแอตทริบิวต์ OBJ ในต้นแบบหลัก เมื่อวัตถุเด็กปรับเปลี่ยน obj.a ก่อนอื่นจะอ่านการอ้างอิงถึง OBJ ในต้นแบบ ในเวลานี้ child.obj และ parent.prototype.obj ชี้ไปที่วัตถุเดียวกันดังนั้นการปรับเปลี่ยนของเด็กของ OBJ.A จะส่งผลกระทบต่อค่าของ parent.prototype.obj.a และส่งผลกระทบต่อวัตถุของคลาสแม่ วิธีการสืบทอดการทำรังของ $ SCOPE ใน AngularJS นั้นถูกนำมาใช้โดยการสร้างแบบจำลองต้นแบบการสืบทอดการสืบทอดใน Javasript
ตามคำอธิบายข้างต้นตราบใดที่ต้นแบบที่เข้าถึงในวัตถุ subclass นั้นเหมือนกับต้นแบบคลาสแม่จะเกิดขึ้นสถานการณ์ข้างต้นจะเกิดขึ้น ดังนั้นเราสามารถคัดลอกต้นแบบคลาสแม่จากนั้นกำหนดให้กับต้นแบบ subclass ด้วยวิธีนี้เมื่อ subclass ปรับเปลี่ยนคุณสมบัติในต้นแบบมันจะปรับเปลี่ยนสำเนาของต้นแบบคลาสแม่เท่านั้นและจะไม่ส่งผลกระทบต่อต้นแบบคลาสแม่ การใช้งานเฉพาะมีดังนี้:
การคัดลอกรหัสมีดังนี้:
var deepClone = function (แหล่งที่มาเป้าหมาย) {
แหล่งที่มา = แหล่งที่มา || -
var toStr = object.prototype.toString
arrstr = '[อาร์เรย์วัตถุ]';
สำหรับ (var i ในแหล่งที่มา) {
if (source.hasownproperty (i)) {
รายการ var = แหล่งที่มา [i];
if (typeof item === 'Object') {
Target [i] = (toStr.apply (รายการ) .toLowerCase () === arrstr): []? -
DeepClone (รายการ, เป้าหมาย [i]);
}อื่น{
DeepClone (รายการ, เป้าหมาย [i]);
-
-
-
เป้าหมายกลับ;
-
var parent = function (ชื่อ) {
this.name = name || 'พ่อแม่' ;
-
parent.prototype.getName = function () {
ส่งคืนสิ่งนี้ชื่อ;
-
parent.prototype.obj = {a: '1'};
var child = function (ชื่อ) {
parent.apply (นี่, อาร์กิวเมนต์);
-
child.prototype = deepClone (parent.prototype);
var child = เด็กใหม่ ('เด็ก');
var parent = parent ใหม่ ('parent');
console.log (child.obj.a); // 1
console.log (parent.obj.a); // 1
child.obj.a = '2';
console.log (child.obj.a); // 2
console.log (parent.obj.a); // 1
จากการพิจารณาทั้งหมดข้างต้นการใช้งานเฉพาะของการสืบทอด JavaScript มีดังนี้ เมื่อมีการพิจารณาว่าเด็กและผู้ปกครองได้รับการพิจารณา:
การคัดลอกรหัสมีดังนี้:
var deepClone = function (แหล่งที่มาเป้าหมาย) {
แหล่งที่มา = แหล่งที่มา || -
var toStr = object.prototype.toString
arrstr = '[อาร์เรย์วัตถุ]';
สำหรับ (var i ในแหล่งที่มา) {
if (source.hasownproperty (i)) {
รายการ var = แหล่งที่มา [i];
if (typeof item === 'Object') {
Target [i] = (toStr.apply (รายการ) .toLowerCase () === arrstr): []? -
DeepClone (รายการ, เป้าหมาย [i]);
}อื่น{
DeepClone (รายการ, เป้าหมาย [i]);
-
-
-
เป้าหมายกลับ;
-
var extend = function (parent, child) {
เด็ก = เด็ก || การทำงาน(){} ;
if (parent === ไม่ได้กำหนด)
เด็กกลับ;
// ยืมตัวสร้างคลาสแม่
child = function () {
parent.apply (นี้, อาร์กิวเมนต์);
-
// สืบทอดต้นแบบคลาสแม่ผ่านสำเนาลึก
child.prototype = deepClone (parent.prototype);
// รีเซ็ตแอตทริบิวต์ตัวสร้าง
child.prototype.constructor = เด็ก;
-
สรุป
ในความเป็นจริงแล้วการใช้มรดกในจาวาสคริปต์นั้นมีความยืดหยุ่นและหลากหลายมากและไม่มีวิธีที่ดีที่สุด วิธีการสืบทอดที่แตกต่างกันจะต้องดำเนินการตามความต้องการที่แตกต่างกัน สิ่งที่สำคัญที่สุดคือการเข้าใจหลักการของการใช้มรดกในจาวาสคริปต์นั่นคือปัญหาของต้นแบบและห่วงโซ่ต้นแบบ ตราบใดที่คุณเข้าใจสิ่งเหล่านี้คุณสามารถใช้มรดกได้อย่างง่ายดาย