ประโยชน์ที่ได้รับจากการถ่ายทอดมรดกต้นแบบในยุคคลาสสิค?


271

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



ความเป็นไปได้ที่ซ้ำกันของการทำความเข้าใจมรดกต้นแบบใน JavaScript
John Slegers

คำตอบ:


560

ฉันรู้ว่าคำตอบนี้คือ 3 ปีปลาย แต่ผมคิดว่าคำตอบที่ปัจจุบันไม่ได้ให้ข้อมูลเพียงพอเกี่ยวกับวิธีการรับมรดก prototypal ดีกว่ามรดกคลาสสิก

ก่อนอื่นเรามาดูข้อโต้แย้งที่พบบ่อยที่สุดของโปรแกรมจาวาสคริปต์ในการป้องกันการรับมรดกต้นแบบ (ฉันกำลังนำข้อโต้แย้งเหล่านี้มาจากกลุ่มคำตอบปัจจุบัน):

  1. มันง่ายมาก
  2. มันทรงพลัง
  3. มันนำไปสู่รหัสที่เล็กลงและซ้ำซ้อนน้อยลง
  4. มันเป็นแบบไดนามิกและมันจะดีกว่าสำหรับภาษาแบบไดนามิก

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

ฉันคิดว่าปัญหาเกี่ยวกับการรับมรดกต้นแบบคือมันอธิบายได้จากมุมมองของ JavaScript ฉันรัก JavaScript แต่การสืบทอดต้นแบบใน JavaScript ไม่ถูกต้อง ซึ่งแตกต่างจากการสืบทอดดั้งเดิมมีสองรูปแบบของการสืบทอดต้นแบบ:

  1. รูปแบบต้นแบบของมรดกต้นแบบ
  2. รูปแบบตัวสร้างของการสืบทอดต้นแบบ

น่าเสียดายที่ JavaScript ใช้รูปแบบคอนสตรัคเตอร์ของการสืบทอดต้นแบบ เนื่องจากเมื่อมีการสร้าง JavaScript เบรนแดนอีช (ผู้สร้าง JS) ต้องการให้ดูเหมือน Java (ซึ่งมีการสืบทอดคลาสสิก):

และเราได้ผลักดันมันในฐานะน้องชายคนเล็กสู่ Java เนื่องจากภาษาที่สมบูรณ์เช่น Visual Basic คือ C ++ ในตระกูลภาษาของ Microsoft ในเวลานั้น

สิ่งนี้ไม่ดีเพราะเมื่อคนใช้ Constructor ใน JavaScript พวกเขาคิดว่า Constructor ที่สืบทอดมาจาก Constructor อื่น ๆ นี่เป็นสิ่งที่ผิด ในวัตถุต้นแบบมรดกสืบทอดจากวัตถุอื่น ช่างก่อสร้างไม่เคยเข้ามาในรูปภาพ นี่คือสิ่งที่คนส่วนใหญ่สับสน

ผู้คนจากภาษาเช่น Java ซึ่งมีการสืบทอดคลาสสิกสับสนมากขึ้นเพราะถึงแม้ว่าคอนสตรัคเตอร์จะดูเหมือนคลาสที่ไม่ทำงานเหมือนคลาส ตามที่Douglas Crockfordกล่าวว่า:

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

ที่นั่นคุณมีมัน ตรงจากปากม้า

มรดกต้นแบบที่แท้จริง

การสืบทอดต้นแบบเป็นเรื่องเกี่ยวกับวัตถุ วัตถุสืบทอดคุณสมบัติจากวัตถุอื่น ๆ นั่นคือทั้งหมดที่มีให้มัน มีสองวิธีในการสร้างวัตถุโดยใช้การสืบทอดต้นแบบ:

  1. สร้างวัตถุใหม่
  2. โคลนวัตถุที่มีอยู่และขยาย

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

พอคุยกัน ลองดูตัวอย่าง ว่าฉันมีวงกลมรัศมี5:

var circle = {
    radius: 5
};

เราสามารถคำนวณพื้นที่และเส้นรอบวงของวงกลมจากรัศมี:

circle.area = function () {
    var radius = this.radius;
    return Math.PI * radius * radius;
};

circle.circumference = function () {
    return 2 * Math.PI * this.radius;
};

10ตอนนี้ผมต้องการที่จะสร้างวงกลมรัศมีอีก วิธีหนึ่งในการทำเช่นนี้คือ:

var circle2 = {
    radius: 10,
    area: circle.area,
    circumference: circle.circumference
};

อย่างไรก็ตาม JavaScript ให้เป็นวิธีที่ดีกว่า - คณะผู้แทน Object.createฟังก์ชั่นที่ใช้ในการทำเช่นนี้:

var circle2 = Object.create(circle);
circle2.radius = 10;

นั่นคือทั้งหมดที่ คุณเพิ่งได้รับมรดกต้นแบบใน JavaScript ไม่ง่ายอย่างนั้นเหรอ? คุณนำวัตถุโคลนมันเปลี่ยนแปลงอะไรก็ตามที่คุณต้องการและไงล่ะ - คุณได้วัตถุใหม่เอี่ยม

ตอนนี้คุณอาจถามว่า "มันง่ายขนาดนี้ได้อย่างไรทุกครั้งที่ฉันต้องการสร้างวงกลมใหม่ที่ฉันต้องการโคลนcircleและกำหนดรัศมีด้วยตนเอง" ทางแก้คือใช้ฟังก์ชั่นในการยกของหนัก:

function createCircle(radius) {
    var newCircle = Object.create(circle);
    newCircle.radius = radius;
    return newCircle;
}

var circle2 = createCircle(10);

ในความเป็นจริงคุณสามารถรวมทั้งหมดนี้เป็นวัตถุเดียวตามตัวอักษรดังนี้

var circle = {
    radius: 5,
    create: function (radius) {
        var circle = Object.create(this);
        circle.radius = radius;
        return circle;
    },
    area: function () {
        var radius = this.radius;
        return Math.PI * radius * radius;
    },
    circumference: function () {
        return 2 * Math.PI * this.radius;
    }
};

var circle2 = circle.create(10);

การสร้างต้นแบบใน JavaScript

หากคุณสังเกตเห็นในโปรแกรมข้างต้นcreateฟังก์ชั่นสร้างโคลนของcircleให้กำหนดใหม่radiusให้กับมันแล้วส่งกลับ นี่คือสิ่งที่คอนสตรัคทำใน JavaScript:

function Circle(radius) {
    this.radius = radius;
}

Circle.prototype.area = function () {
    var radius = this.radius;
    return Math.PI * radius * radius;
};

Circle.prototype.circumference = function () {         
    return 2 * Math.PI * this.radius;
};

var circle = new Circle(5);
var circle2 = new Circle(10);

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

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

แทนที่จะคิดว่าวัตถุที่สืบทอดมาจากวัตถุอื่น ๆ พวกเขาคิดว่าตัวสร้างที่สืบทอดมาจากตัวสร้างอื่น ๆ และจากนั้นกลายเป็นสับสนอย่างเต็มที่

มีเหตุผลอื่นอีกมากมายที่ทำให้รูปแบบตัวสร้างใน JavaScript ควรหลีกเลี่ยง คุณสามารถอ่านเกี่ยวกับพวกเขาได้ในบล็อกโพสต์ของฉันที่นี่: Constructors vs Prototypes


แล้วประโยชน์ของการรับมรดกต้นแบบเหนือมรดกคลาสสิกคืออะไร? Let 's Go ผ่านข้อโต้แย้งที่พบมากที่สุดอีกครั้งและอธิบายว่าทำไม

1. การสืบทอดต้นแบบเป็นเรื่องง่าย

CMSระบุไว้ในคำตอบของเขา:

ในความคิดของฉันประโยชน์ที่สำคัญของการสืบทอดมรดกต้นแบบคือความเรียบง่าย

ลองพิจารณาสิ่งที่เราเพิ่งทำ เราได้สร้างวัตถุที่มีรัศมีของcircle จากนั้นเราก็โคลนและให้โคลนรัศมีของ510

ด้วยเหตุนี้เราต้องการเพียงสองสิ่งเท่านั้นที่จะทำให้มรดกเป็นแบบอย่าง:

  1. วิธีสร้างวัตถุใหม่ (เช่นตัวอักษรของวัตถุ)
  2. วิธีการขยายวัตถุที่มีอยู่ (เช่นObject.create)

ในทางตรงกันข้ามมรดกคลาสสิกมีความซับซ้อนมากขึ้น ในการสืบทอดแบบคลาสสิกคุณมี:

  1. การเรียนการสอน
  2. วัตถุ.
  3. อินเตอร์เฟซ
  4. ชั้นเรียนนามธรรม
  5. ชั้นเรียนสุดท้าย
  6. คลาสฐานเสมือน
  7. ก่อสร้าง
  8. destructors

คุณได้รับความคิด ประเด็นก็คือมรดกต้นแบบนั้นง่ายต่อการเข้าใจง่ายต่อการนำไปปฏิบัติและเหตุผลที่ง่ายกว่า

ดังที่ Steve Yegge วางไว้ในโพสต์บล็อกสุดคลาสสิค " Portrait of a N00b ":

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

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

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

ตามที่ฉันได้กล่าวไปแล้วชั้นเรียนให้ความรู้สึกผิด ๆ กับความปลอดภัย ตัวอย่างเช่นคุณได้รับมากเกินไปNullPointerExceptionใน Java แม้เมื่อรหัสของคุณชัดเจน ฉันพบว่าการสืบทอดคลาสสิกมักจะได้รับในทางของการเขียนโปรแกรม แต่อาจเป็นเพียง Java Python มีระบบการสืบทอดแบบคลาสสิกที่น่าทึ่ง

2. การสืบทอดต้นแบบมีประสิทธิภาพ

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

  1. ตัวแปรส่วนตัว
  2. หลายมรดก

การอ้างสิทธิ์นี้เป็นเท็จ เรารู้อยู่แล้วว่า JavaScript รองรับตัวแปรส่วนตัวผ่านการปิดแต่สิ่งที่เกี่ยวกับการสืบทอดหลาย ๆ วัตถุใน JavaScript มีต้นแบบเดียวเท่านั้น

ความจริงก็คือการถ่ายทอดมรดกต้นแบบสนับสนุนการสืบทอดจากต้นแบบหลาย การสืบทอดต้นแบบนั้นหมายถึงวัตถุหนึ่งที่สืบทอดจากวัตถุอื่น จริงๆแล้วมีสองวิธีในการนำมรดกต้นแบบไปใช้ :

  1. การมอบหมายหรือการสืบทอดที่แตกต่างกัน
  2. การโคลนหรือการต่อเชื่อมแบบสืบทอด

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

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

function copyOf(object, prototype) {
    var prototypes = object.prototypes;
    var prototypeOf = Object.isPrototypeOf;
    return prototypes.indexOf(prototype) >= 0 ||
        prototypes.some(prototypeOf, prototype);
}

ดังนั้นการรับมรดกต้นแบบมีประสิทธิภาพเท่ากับมรดกดั้งเดิม ในความเป็นจริงมันมีประสิทธิภาพมากกว่าการสืบทอดแบบคลาสสิกเพราะในการรับมรดกต้นแบบคุณสามารถเลือกคุณสมบัติที่จะคัดลอกและคุณสมบัติที่จะละเว้นจากต้นแบบที่แตกต่างกัน

ในการสืบทอดแบบคลาสสิกเป็นไปไม่ได้ (หรืออย่างน้อยยากมาก) ในการเลือกคุณสมบัติที่คุณต้องการรับช่วง พวกเขาใช้ฐานเรียนเสมือนจริงและอินเตอร์เฟซในการแก้ปัญหาเพชร

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

3. การสืบทอดต้นแบบมีน้อยลงซ้ำซ้อน

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

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

Java เป็นที่รู้จักสำหรับพฤติกรรมนี้ ฉันจำBob Nystromได้อย่างชัดเจนโดยกล่าวถึงเกร็ดเล็กเกร็ดน้อยต่อไปนี้ในโพสต์บล็อกของเขาเกี่ยวกับPratt Parsers :

คุณต้องรัก Java ของ "โปรดลงนามในระดับสี่เท่า" ของระบบราชการที่นี่

อีกครั้งฉันคิดว่าเป็นเพียงเพราะ Java ดูดมาก

ข้อโต้แย้งที่ถูกต้องอย่างหนึ่งคือไม่ใช่ทุกภาษาที่มีการสืบทอดแบบดั้งเดิมสนับสนุนการสืบทอดหลายแบบ อีกครั้ง Java อยู่ในใจ ใช่ Java มีอินเตอร์เฟส แต่นั่นไม่เพียงพอ บางครั้งคุณต้องการมรดกหลายอย่าง

เนื่องจากการสืบทอดแบบต้นแบบอนุญาตให้มีการสืบทอดหลายครั้ง, รหัสที่ต้องใช้การสืบทอดหลายแบบจะมีความซ้ำซ้อนน้อยกว่าถ้าเขียนโดยใช้การสืบทอดแบบต้นแบบมากกว่าในภาษาที่มีการสืบทอดแบบดั้งเดิม แต่ไม่มีการสืบทอดหลายแบบ

4. การสืบทอดต้นแบบเป็นแบบไดนามิก

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

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

ข้อสรุป

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

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

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

ถ้าคุณชอบคำตอบนี้คุณควรอ่านโพสต์บล็อกของฉันในหัวข้อ " Why Prototypal Intersitance Matters " เชื่อฉันสิคุณจะไม่ผิดหวัง


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

7
ฉันเห็นด้วยอย่างสมบูรณ์กับสิ่งที่คุณพูด ฉันรู้สึกว่าโปรแกรมเมอร์มากเกินไปปฏิเสธ JavaScript เนื่องจากไม่มีการสืบทอดแบบคลาสสิกหรือบอกว่ามันเป็นภาษาที่ง่ายและเป็นใบ้ จริงๆแล้วมันเป็นแนวคิดที่ทรงพลังอย่างยิ่งฉันเห็นด้วยและโปรแกรมเมอร์หลายคนควรยอมรับและเรียนรู้มัน จากที่กล่าวมาฉันรู้สึกว่ามีนักพัฒนาจำนวนมากในจาวาสคริปต์ที่ต่อต้านการสืบทอดแบบคลาสสิกทุกรูปแบบรวมกันอยู่ในจาวาสคริปต์เมื่อพวกเขาไม่มีพื้นฐานสำหรับข้อโต้แย้งของพวกเขาเลย ทั้งสองมีพลังเท่าเทียมกันในสิทธิของตนเองและมีประโยชน์เท่าเทียมกัน
Redline

9
นั่นคือความคิดเห็นของคุณ แต่ฉันจะไม่เห็นด้วยอย่างต่อเนื่องและฉันคิดว่าความนิยมที่เพิ่มขึ้นของสิ่งต่าง ๆ เช่น CoffeeScript และ TypeScript แสดงให้เห็นว่ามีชุมชนขนาดใหญ่ของนักพัฒนาที่ต้องการใช้ฟังก์ชันนี้เมื่อเหมาะสม อย่างที่คุณพูด ES6 จะเพิ่มน้ำตาลซินแทคติค แต่ก็ยังไม่ได้ให้ความครอบคลุมของ jTypes ในบันทึกย่อด้านข้างฉันไม่ได้เป็นคนรับผิดชอบต่อ downvote ของคุณ ในขณะที่ฉันไม่เห็นด้วยกับคุณฉันไม่รู้สึกว่ามันถือว่าคุณมีคำตอบที่ไม่ดี คุณถี่ถ้วนมาก
Redline

25
คุณใช้คำว่าโคลนมากซึ่งผิดปกติ Object.createกำลังสร้างวัตถุใหม่ด้วยต้นแบบที่ระบุ การเลือกคำพูดของคุณจะสร้างความประทับใจว่าเครื่องต้นแบบถูกโคลน
Pavel Horal

7
@Aadit: ไม่จำเป็นต้องมีการป้องกันจริงๆ คำตอบของคุณมีรายละเอียดมากและสมควรได้รับคะแนนโหวต ฉันไม่ได้แนะนำว่า "การเชื่อมโยง" ควรเป็นการแทนที่แบบ "โคลน" แต่มันจะอธิบายการเชื่อมต่อระหว่างวัตถุและต้นแบบที่เหมาะสมได้ดีกว่าไม่ว่าคุณจะยืนยันนิยามของคำว่า "โคลน" หรือไม่ " หรือไม่. เปลี่ยนหรือไม่เปลี่ยนมันเป็นทางเลือกของคุณ
Andy E

42

ให้ฉันตอบคำถามแบบอินไลน์ได้จริง

การสืบทอดต้นแบบมีข้อดีดังต่อไปนี้:

  1. มันเหมาะกว่าสำหรับภาษาแบบไดนามิกเนื่องจากการสืบทอดนั้นเป็นแบบไดนามิกตามสภาพแวดล้อมที่มีอยู่ (การบังคับใช้กับ JavaScript ควรชัดเจนที่นี่) สิ่งนี้อนุญาตให้คุณทำสิ่งต่าง ๆ ได้อย่างรวดเร็วเช่นเดียวกับการปรับแต่งคลาสโดยไม่มีรหัสโครงสร้างพื้นฐานจำนวนมาก .
  2. มันง่ายกว่าที่จะใช้โครงร่างวัตถุต้นแบบกว่าคลาส / วัตถุขั้วคู่แบบคลาสสิก
  3. มันไม่จำเป็นต้องมีขอบคมที่ซับซ้อนรอบตัวแบบวัตถุเช่น "metaclasses" (ฉันไม่เคย metaclass ที่ฉันชอบ ... ขอโทษ!) หรือ "ค่าลักษณะเฉพาะ" หรือสิ่งที่คล้ายกัน

มันมีข้อเสียดังต่อไปนี้:

  1. การตรวจสอบชนิดของภาษาต้นแบบนั้นเป็นไปไม่ได้ แต่เป็นเรื่องยากมาก "การตรวจสอบประเภท" ส่วนใหญ่ของภาษาต้นแบบคือการตรวจสอบสไตล์แบบเป็ด ไม่เหมาะกับทุกสภาพแวดล้อม
  2. มันก็ยากที่จะทำสิ่งต่าง ๆ เช่นการเพิ่มประสิทธิภาพวิธีการจัดส่งโดยการวิเคราะห์แบบคงที่ (หรือบ่อยครั้งแม้แต่แบบไดนามิก!) มันสามารถ (ฉันเครียด: สามารถ ) จะไม่มีประสิทธิภาพมากได้อย่างง่ายดายมาก
  3. การสร้างวัตถุในทำนองเดียวกันสามารถ (และมักจะ) ช้ากว่ามากในภาษาต้นแบบมากกว่าที่จะอยู่ในรูปแบบคลาส / วัตถุขั้วคู่แบบดั้งเดิม

ฉันคิดว่าคุณสามารถอ่านระหว่างบรรทัดด้านบนและเกิดข้อได้เปรียบและข้อเสียของแผนการคลาส / วัตถุแบบดั้งเดิม มีแน่นอนมากขึ้นในแต่ละพื้นที่ดังนั้นฉันจะปล่อยให้คนอื่น ๆ ที่เหลือตอบ


1
เฮ้ดูคำตอบสั้น ๆ ที่ไม่ใช่แฟนบอย หวังว่านี่เป็นคำตอบที่ดีที่สุดสำหรับคำถามนี้
siliconrockstar

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

28

IMO ประโยชน์หลักของการถ่ายทอดทางพันธุกรรมคือความเรียบง่าย

ธรรมชาติ prototypal ของภาษาที่สามารถสร้างความสับสนให้คนที่มีความคลาสสิกที่ได้รับการฝึกฝน แต่มันกลับกลายเป็นว่าจริงนี้เป็นจริงๆแนวคิดที่เรียบง่ายและมีประสิทธิภาพมรดกค่า

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

หากคุณคิดว่าเป็นแม่แบบคุณจะสังเกตเห็นว่าคุณไม่ต้องการคลาส ...

การสืบทอดต้นแบบจะได้รับความนิยมมากขึ้นในอนาคตอันใกล้นี้ข้อกำหนดของECMAScript 5th EditionนำเสนอObject.createวิธีการซึ่งช่วยให้คุณสามารถสร้างอินสแตนซ์ของวัตถุใหม่ที่สืบทอดมาจากอีกวิธีหนึ่งได้ง่ายๆ:

var obj = Object.create(baseInstance);

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


11
"รหัสของคุณเล็กลงซ้ำซ้อนน้อยลง ... " ทำไม? ฉันได้ดูที่วิกิพีเดียลิงค์สำหรับ "การสืบทอดที่แตกต่างกัน" และไม่มีอะไรสนับสนุนการยืนยันเหล่านี้ ทำไมการสืบทอดแบบคลาสสิกจึงส่งผลให้มีโค้ดที่ใหญ่กว่า
Noel Abrahams

4
ฉันเห็นด้วยกับ Noel การถ่ายทอดทางพันธุกรรมเป็นเพียงวิธีหนึ่งในการทำงานให้สำเร็จ แต่นั่นก็ไม่ได้ทำให้ถูกต้อง เครื่องมือต่าง ๆ จะทำงานในรูปแบบต่างกันสำหรับงานที่แตกต่างกัน มรดกต้นแบบมีสถานที่ มันเป็นแนวคิดที่ทรงพลังและเรียบง่ายอย่างยิ่ง เมื่อกล่าวถึงการขาดการสนับสนุน encapsulation และ polymorphism ที่แท้จริงทำให้จาวาสคริปต์เสียเปรียบอย่างมาก วิธีการเหล่านี้ใช้เวลานานกว่า JavaScript นานกว่าและมีหลักการในการใช้งาน ดังนั้นการคิดต้นแบบคือ "ดีกว่า" เป็นเพียงความคิดผิดทั้งหมด
Redline

1
คุณสามารถจำลองการสืบทอดตามคลาสโดยใช้การสืบทอดที่อิงกับต้นแบบ แต่ไม่ใช่ในทางกลับกัน นั่นอาจเป็นข้อโต้แย้งที่ดี นอกจากนี้ฉันเห็นการห่อหุ้มเป็นแบบแผนมากกว่าคุณลักษณะภาษา (โดยปกติคุณสามารถทำลายการห่อหุ้มด้วยการสะท้อนกลับ) เกี่ยวกับความแตกต่าง - สิ่งที่คุณได้รับคือไม่ต้องเขียนเงื่อนไข "ถ้า" อย่างง่ายเมื่อตรวจสอบการขัดแย้งของเมธอด (และความเร็วเล็กน้อยหากวิธีเป้าหมายได้รับการแก้ไขระหว่างการรวบรวม) ไม่มีข้อเสีย JavaScript จริงที่นี่
Pavel Horal

ต้นแบบนั้นยอดเยี่ยม IMO ฉันกำลังคิดที่จะสร้างภาษาที่ใช้งานได้เช่น Haskell ... แต่แทนที่จะสร้าง abstractions ฉันจะยึดทุกอย่างไว้ที่ต้นแบบ แทนการสรุปผลรวมและแฟกทอเรียลเป็น "รอยพับ" คุณควรเป็นต้นแบบของฟังก์ชันผลรวมและแทนที่ด้วย + ด้วย * และ 0 ด้วย 1 เพื่อสร้างผลิตภัณฑ์ ฉันจะอธิบาย "Monads" เป็นแบบอย่างที่แทนที่ "จากนั้น" จากคำสัญญา / โทรกลับด้วย flatMap เป็นคำพ้องความหมายของ "จากนั้น" ฉันคิดว่าต้นแบบสามารถช่วยนำการเขียนโปรแกรมที่ใช้งานได้มาสู่คนจำนวนมาก
aoeu256

11

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

prototypal

var single = { status: "Single" },
    princeWilliam = Object.create(single),
    cliffRichard = Object.create(single);

console.log(Object.keys(princeWilliam).length); // 0
console.log(Object.keys(cliffRichard).length); // 0

// Marriage event occurs
princeWilliam.status = "Married";

console.log(Object.keys(princeWilliam).length); // 1 (New instance property)
console.log(Object.keys(cliffRichard).length); // 0 (Still refers to prototype)

คลาสสิกด้วยวิธีการอินสแตนซ์ (ไม่มีประสิทธิภาพเพราะแต่ละอินสแตนซ์เก็บไว้เป็นทรัพย์สินของตัวเอง)

function Single() {
    this.status = "Single";
}

var princeWilliam = new Single(),
    cliffRichard = new Single();

console.log(Object.keys(princeWilliam).length); // 1
console.log(Object.keys(cliffRichard).length); // 1

คลาสสิกที่มีประสิทธิภาพ

function Single() {
}

Single.prototype.status = "Single";

var princeWilliam = new Single(),
    cliffRichard = new Single();

princeWilliam.status = "Married";

console.log(Object.keys(princeWilliam).length); // 1
console.log(Object.keys(cliffRichard).length); // 0
console.log(cliffRichard.status); // "Single"

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


2
เมื่อดูที่คำตอบและแหล่งข้อมูลอื่น ๆ เกี่ยวกับหัวข้อนี้คำตอบของคุณจะระบุว่า: "การถ่ายทอดทาง Prototypal เป็นส่วนหนึ่งของน้ำตาล syntactic ที่ถูกเพิ่มเข้าไปใน JavaScript เพื่อให้สามารถดูการสืบทอดแบบคลาสสิก" OP ดูเหมือนจะถามเกี่ยวกับประโยชน์ของการสืบทอดต้นแบบใน JS เกี่ยวกับการสืบทอดดั้งเดิมในภาษาอื่นแทนที่จะเปรียบเทียบเทคนิคการสร้างอินสแตนซ์ภายใน JavaScript
เฟดเดอร์เข้ม

2

การพัฒนาเว็บ: การสืบทอดต้นแบบกับการสืบทอดคลาสสิก

http://chamnapchhorn.blogspot.com/2009/05/prototypal-inheritance-vs-classical.html

Vs มรดกดั้งเดิม Prototypal - Stack ล้น

Vs มรดกดั้งเดิม


20
ฉันคิดว่ามันเป็นการดีที่จะสรุปเนื้อหาของลิงก์แทนที่จะวางลิงก์ (สิ่งที่ตัวเองเคยทำ) ยกเว้นลิงก์อื่นดังนั้น เนื่องจากลิงค์ / ไซต์ต่างๆลดลงและคุณสูญเสียการตอบคำถามและอาจส่งผลต่อผลการค้นหา
James Westgate

ลิงค์ที่ 1 ไม่ตอบคำถามที่ว่าทำไมการรับมรดกต้นแบบ? มันอธิบายได้ง่าย
viebel
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.