ตัวอย่างที่ดีของการสืบทอดต้นแบบของ JavaScript


89

ฉันเขียนโปรแกรมด้วยภาษา OOP มานานกว่า 10 ปีแล้ว แต่ตอนนี้ฉันกำลังเรียนรู้ JavaScript และนี่เป็นครั้งแรกที่ฉันได้พบกับการสืบทอดจากต้นแบบ ฉันมักจะเรียนรู้ได้เร็วที่สุดโดยการศึกษาโค้ดที่ดี อะไรคือตัวอย่างของแอปพลิเคชัน JavaScript (หรือไลบรารี) ที่ใช้การสืบทอดต้นแบบอย่างถูกต้อง และคุณสามารถอธิบาย (สั้น ๆ ) ได้อย่างไรว่าใช้การถ่ายทอดทางพันธุกรรมของต้นแบบอย่างไร / ที่ไหนฉันจึงรู้ว่าจะเริ่มอ่านได้ที่ไหน


1
คุณมีโอกาสตรวจสอบห้องสมุดฐานหรือไม่? มันดีจริงๆและค่อนข้างเล็ก ถ้าคุณชอบลองทำเครื่องหมายคำตอบของฉันเป็นคำตอบ TIA โรแลนด์
Roland Bouman

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

คำตอบ:


48

Douglas Crockford มีเพจที่ดีเกี่ยวกับการสืบทอด JavaScript Prototypal :

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

Dean Edward's Base.js , Mootools's Classหรืองาน Simple Inheritance ของ John Resigเป็นวิธีการสืบทอดคลาสสิกใน JavaScript


ทำไมไม่เพียงแค่newObj = Object.create(oldObj);ถ้าคุณต้องการฟรีคลาส? มิฉะนั้นแทนที่ด้วยoldObjวัตถุต้นแบบของฟังก์ชันตัวสร้างควรใช้งานได้หรือไม่?
Cyker

76

ดังที่ได้กล่าวไว้ภาพยนตร์ของ Douglas Crockford ให้คำอธิบายที่ดีเกี่ยวกับสาเหตุและครอบคลุมถึงวิธีการ แต่หากต้องการวางไว้ใน JavaScript สองสามบรรทัด:

// Declaring our Animal object
var Animal = function () {

    this.name = 'unknown';

    this.getName = function () {
        return this.name;
    }

    return this;
};

// Declaring our Dog object
var Dog = function () {

    // A private variable here        
    var private = 42;

    // overriding the name
    this.name = "Bello";

    // Implementing ".bark()"
    this.bark = function () {
        return 'MEOW';
    }  

    return this;
};


// Dog extends animal
Dog.prototype = new Animal();

// -- Done declaring --

// Creating an instance of Dog.
var dog = new Dog();

// Proving our case
console.log(
    "Is dog an instance of Dog? ", dog instanceof Dog, "\n",
    "Is dog an instance of Animal? ", dog instanceof Animal, "\n",
    dog.bark() +"\n", // Should be: "MEOW"
    dog.getName() +"\n", // Should be: "Bello"
    dog.private +"\n" // Should be: 'undefined'
);

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

// Defining test one, prototypal
var testOne = function () {};
testOne.prototype = (function () {
    var me = {}, privateVariable = 42;
    me.someMethod = function () {
        return privateVariable;
    };

    me.publicVariable = "foo bar";
    me.anotherMethod = function () {
        return this.publicVariable;
    };

    return me;

}());


// Defining test two, function
var testTwo = ​function() {
    var me = {}, privateVariable = 42;
    me.someMethod = function () {
        return privateVariable;
    };

    me.publicVariable = "foo bar";
    me.anotherMethod = function () {
        return this.publicVariable;
    };

    return me;
};


// Proving that both techniques are functionally identical
var resultTestOne = new testOne(),
    resultTestTwo = new testTwo();

console.log(
    resultTestOne.someMethod(), // Should print 42
    resultTestOne.publicVariable // Should print "foo bar"
);

console.log(
    resultTestTwo.someMethod(), // Should print 42
    resultTestTwo.publicVariable // Should print "foo bar"
);



// Performance benchmark start
var stop, start, loopCount = 1000000;

// Running testOne
start = (new Date()).getTime(); 
for (var i = loopCount; i>0; i--) {
    new testOne();
}
stop = (new Date()).getTime();

console.log('Test one took: '+ Math.round(((stop/1000) - (start/1000))*1000) +' milliseconds');



// Running testTwo
start = (new Date()).getTime(); 
for (var i = loopCount; i>0; i--) {
    new testTwo();
}
stop = (new Date()).getTime();

console.log('Test two took: '+ Math.round(((stop/1000) - (start/1000))*1000) +' milliseconds');

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


3
โปรดทราบว่าใน testOne privateVariableเป็นเพียงตัวแปรในขอบเขตของIIFEและใช้ร่วมกันในทุกอินสแตนซ์ดังนั้นคุณไม่ควรเก็บข้อมูลเฉพาะอินสแตนซ์ไว้ (ใน testTwo เป็นแบบเจาะจงอินสแตนซ์เนื่องจากการเรียกแต่ละครั้งเพื่อ testTwo () สร้างขอบเขตใหม่ต่ออินสแตนซ์)
shesek

ฉันโหวตให้เพราะคุณแสดงวิธีอื่นและทำไมไม่ใช้เพราะมันทำสำเนา
Murphy316

ปัญหาในการสร้างอ็อบเจกต์ใหม่ทุกครั้งส่วนใหญ่เกิดจากวิธีการสร้างอ็อบเจกต์ใหม่ทุกครั้ง Dog.prototypeแต่เราสามารถบรรเทาปัญหาโดยการกำหนดวิธีการในการ ดังนั้นแทนที่จะใช้this.bark = function () {...}เราสามารถทำDot.prototype.bark = function () {...}นอกDogฟังก์ชันได้ (ดูรายละเอียดเพิ่มเติมในคำตอบนี้ )
C Huang

26
function Shape(x, y) {
    this.x = x;
    this.y = y;
}

// 1. Explicitly call base (Shape) constructor from subclass (Circle) constructor passing this as the explicit receiver
function Circle(x, y, r) {
    Shape.call(this, x, y);
    this.r = r;
}

// 2. Use Object.create to construct the subclass prototype object to avoid calling the base constructor
Circle.prototype = Object.create(Shape.prototype);

3
บางทีการเพิ่มลิงก์นี้พร้อมกับคำตอบของคุณอาจทำให้ภาพสมบูรณ์ยิ่งขึ้น: developer.mozilla.org/th/docs/Web/JavaScript/Reference/…
Dynom

14

ฉันจะดูที่YUIและที่Baseห้องสมุดของ Dean Edward : http://dean.edwards.name/weblog/2006/03/base/

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


YUI 2 เลิกใช้งานไปแล้วในปี 2011 ดังนั้นลิงก์ไปยังlangใช้งานไม่ได้ ใครสนใจที่จะแก้ไขมันสำหรับ YUI 3?
แอ๊

lang ใน yui 3 ดูเหมือนจะไม่มีวิธีขยาย แต่เนื่องจากคำตอบคือตั้งใจที่จะใช้การใช้งานเป็นตัวอย่างเวอร์ชันจึงไม่สำคัญ
eMBee

5

นอกจากนี้ยังมีของ Microsoft ASP.NET Ajax ห้องสมุดhttp://www.asp.net/ajax/

มีจำนวนมากของบทความ MSDN ดีอยู่รอบ ๆ เช่นกันรวมทั้งสร้างการใช้งานเว็บขั้นสูงด้วยเทคนิคเชิงวัตถุ


5

นี่เป็นตัวอย่างที่ชัดเจนที่สุดที่ฉันพบจากหนังสือ Node ของ Mixu ( http://book.mixu.net/node/ch6.html ):

ฉันชอบองค์ประกอบมากกว่าการถ่ายทอดทางพันธุกรรม:

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

หากคุณต้องใช้การสืบทอดอย่างน้อยควรหลีกเลี่ยงการใช้ฟังก์ชันการใช้งาน / เวทมนตร์อื่นที่ไม่เป็นมาตรฐาน นี่คือวิธีที่คุณสามารถใช้การถ่ายทอดทางโทรสารที่เหมาะสมใน ES3 บริสุทธิ์ได้ (ตราบใดที่คุณปฏิบัติตามกฎการไม่กำหนดคุณสมบัติบนต้นแบบ):

function Animal(name) {
  this.name = name;
};
Animal.prototype.move = function(meters) {
  console.log(this.name+" moved "+meters+"m.");
};

function Snake() {
  Animal.apply(this, Array.prototype.slice.call(arguments));
};
Snake.prototype = new Animal();
Snake.prototype.move = function() {
  console.log("Slithering...");
  Animal.prototype.move.call(this, 5);
};

var sam = new Snake("Sammy the Python");
sam.move();

นี่ไม่ใช่สิ่งเดียวกับการสืบทอดแบบคลาสสิก - แต่เป็น Javascript มาตรฐานที่เข้าใจได้และมีฟังก์ชันการทำงานที่ผู้คนส่วนใหญ่แสวงหา: ตัวสร้าง chainable และความสามารถในการเรียกเมธอดของ superclass


4

ES6 classและextends

ES6 classและextendsเป็นเพียงน้ำตาลทางไวยากรณ์สำหรับการจัดการห่วงโซ่ต้นแบบที่เป็นไปได้ก่อนหน้านี้และเนื้อหาก็เป็นการตั้งค่าที่ยอมรับได้มากที่สุด

ขั้นแรกเรียนรู้เพิ่มเติมเกี่ยวกับห่วงโซ่ต้นแบบและการ.ค้นหาคุณสมบัติได้ที่: https://stackoverflow.com/a/23877420/895245

ตอนนี้เรามาแยกแยะสิ่งที่เกิดขึ้น:

class C {
    constructor(i) {
        this.i = i
    }
    inc() {
        return this.i + 1
    }
}

class D extends C {
    constructor(i) {
        super(i)
    }
    inc2() {
        return this.i + 2
    }
}
// Inheritance syntax works as expected.
(new C(1)).inc() === 2
(new D(1)).inc() === 2
(new D(1)).inc2() === 3
// "Classes" are just function objects.
C.constructor === Function
C.__proto__ === Function.prototype
D.constructor === Function
// D is a function "indirectly" through the chain.
D.__proto__ === C
D.__proto__.__proto__ === Function.prototype
// "extends" sets up the prototype chain so that base class
// lookups will work as expected
var d = new D(1)
d.__proto__ === D.prototype
D.prototype.__proto__ === C.prototype
// This is what `d.inc` actually does.
d.__proto__.__proto__.inc === C.prototype.inc
// Class variables
// No ES6 syntax sugar apparently:
// /programming/22528967/es6-class-variable-alternatives
C.c = 1
C.c === 1
// Because `D.__proto__ === C`.
D.c === 1
// Nothing makes this work.
d.c === undefined

แผนภาพที่เรียบง่ายโดยไม่มีวัตถุที่กำหนดไว้ล่วงหน้าทั้งหมด:

      __proto__
(C)<---------------(D)         (d)
| |                |           |
| |                |           |
| |prototype       |prototype  |__proto__
| |                |           |
| |                |           |
| |                | +---------+
| |                | |
| |                | |
| |                v v
|__proto__        (D.prototype)
| |                |
| |                |
| |                |__proto__
| |                |
| |                |
| | +--------------+
| | |
| | |
| v v
| (C.prototype)--->(inc)
|
v
Function.prototype


1

ตัวอย่างที่ดีที่สุดที่ผมเคยเห็นอยู่ในดักลาส Crockford ของJavaScript: ส่วนดี การซื้อเพื่อช่วยให้คุณมีมุมมองที่สมดุลกับภาษาอย่างแน่นอน

Douglas Crockfordรับผิดชอบรูปแบบ JSON และทำงานที่ Yahoo ในฐานะกูรู JavaScript


7
รับผิดชอบ? ที่ฟังดูเหมือน "รู้สึกผิด" :)
Roland Bouman

@Roland ฉันคิดว่า JSON เป็นรูปแบบที่ดีมากสำหรับการจัดเก็บข้อมูล เขาไม่ได้คิดค้นมันขึ้นมาอย่างแน่นอนรูปแบบนี้มีไว้สำหรับการตั้งค่าการกำหนดค่าใน Steam ในปี 2002
Chris S

Chris S ฉันก็คิดเช่นนั้นบ่อยขึ้นเรื่อย ๆ ฉันหวังว่าเราจะสามารถข้าม XML ทั้งหมดเป็นรูปแบบการแลกเปลี่ยนและย้ายไปที่ JSON ได้ทันที
Roland Bouman

3
ไม่มีอะไรให้ประดิษฐ์มากนัก: JSON เป็นชุดย่อยของไวยากรณ์ตัวอักษรของวัตถุของ JavaScript ซึ่งเป็นภาษามาตั้งแต่ประมาณปี 1997
Tim Down

@ ช่วงเวลาที่ดี - ฉันไม่รู้ว่ามันมีมาตั้งแต่เริ่มต้น
Chris S


0

การเพิ่มตัวอย่างของ Prototype based inheritance ใน Javascript

// Animal Class
function Animal (name, energy) {
  this.name = name;
  this.energy = energy;
}

Animal.prototype.eat = function (amount) {
  console.log(this.name, "eating. Energy level: ", this.energy);
  this.energy += amount;
  console.log(this.name, "completed eating. Energy level: ", this.energy);
}

Animal.prototype.sleep = function (length) {
  console.log(this.name, "sleeping. Energy level: ", this.energy);
  this.energy -= 1;
  console.log(this.name, "completed sleeping. Energy level: ", this.energy);
}

Animal.prototype.play = function (length) {
  console.log(this.name, " playing. Energy level: ", this.energy);
  this.energy -= length;
  console.log(this.name, "completed playing. Energy level: ", this.energy);
}

// Dog Class
function Dog (name, energy, breed) {
  Animal.call(this, name, energy);
  this.breed = breed;
}

Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;

Dog.prototype.bark = function () {
  console.log(this.name, "barking. Energy level: ", this.energy);
  this.energy -= 1;
  console.log(this.name, "done barking. Energy level: ", this.energy);
}

Dog.prototype.showBreed = function () {
  console.log(this.name,"'s breed is ", this.breed);
}

// Cat Class
function Cat (name, energy, male) {
  Animal.call(this, name, energy);
  this.male = male;
}

Cat.prototype = Object.create(Animal.prototype);
Cat.prototype.constructor = Cat;

Cat.prototype.meow = function () {
  console.log(this.name, "meowing. Energy level: ", this.energy);
  this.energy -= 1;
  console.log(this.name, "done meowing. Energy level: ", this.energy);
}

Cat.prototype.showGender = function () {
  if (this.male) {
    console.log(this.name, "is male.");
  } else {
    console.log(this.name, "is female.");
  }
}

// Instances
const charlie = new Dog("Charlie", 10, "Labrador");
charlie.bark();
charlie.showBreed();

const penny = new Cat("Penny", 8, false);
penny.meow();
penny.showGender();

ES6 ใช้การใช้งานการสืบทอดที่ง่ายกว่ามากโดยใช้ตัวสร้างและคำหลักขั้นสูง

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