Contoh Bagus Warisan Berbasis Prototipe JavaScript


89

Saya telah memprogram dengan bahasa OOP selama lebih dari 10 tahun tetapi saya belajar JavaScript sekarang dan ini pertama kalinya saya menemukan warisan berbasis prototipe. Saya cenderung belajar tercepat dengan mempelajari kode yang baik. Apa contoh aplikasi JavaScript (atau pustaka) yang ditulis dengan baik yang menggunakan pewarisan prototipe dengan benar? Dan dapatkah Anda menjelaskan (secara singkat) bagaimana / di mana warisan prototipe digunakan, jadi saya tahu harus mulai membaca dari mana?


1
Apakah Anda mendapat kesempatan untuk memeriksa perpustakaan Basis itu? Sangat bagus, dan cukup kecil. Jika Anda menyukainya, pertimbangkan untuk menandai jawaban saya sebagai jawabannya. TIA, roland.
Roland Bouman

Saya kira saya berada di perahu yang sama dengan Anda. Saya juga ingin belajar sedikit tentang bahasa prototipe ini, tidak dibatasi hanya pada kerangka kerja oop atau serupa, bahkan mereka hebat dan semua, kita perlu belajar, bukan? Tidak hanya beberapa framework yang melakukannya untuk saya, bahkan jika saya akan menggunakannya. Tapi pelajari cara membuat hal-hal baru dalam bahasa baru dengan cara baru, pikirkan di luar kotak. Saya menyukai gaya Anda. Saya akan mencoba membantu saya dan mungkin membantu Anda. Segera setelah saya menemukan sesuatu, saya akan memberi tahu Anda.
marcelo-ferraz

Jawaban:


48

Douglas Crockford memiliki halaman yang bagus tentang JavaScript Prototypal Inheritance :

Lima tahun lalu saya menulis Warisan Klasik dalam JavaScript. Ini menunjukkan bahwa JavaScript adalah bahasa prototipe bebas kelas, dan memiliki kekuatan ekspresif yang cukup untuk mensimulasikan sistem klasik. Gaya pemrograman saya telah berkembang sejak saat itu, sebagaimana seharusnya setiap programmer yang baik. Saya telah belajar untuk sepenuhnya merangkul prototypalisme, dan telah membebaskan diri saya dari batasan model klasik.

Base.js Dean Edward , Mootools's Class atau karya Simple Inheritance John Resig adalah cara untuk melakukan inheritance klasik dalam JavaScript.


Mengapa tidak hanya newObj = Object.create(oldObj);jika Anda menginginkannya tanpa kelas? Jika tidak, ganti dengan oldObjobjek prototipe dari fungsi konstruktor harus bekerja?
Cyker

76

Seperti yang telah disebutkan, film-film karya Douglas Crockford memberikan penjelasan yang baik tentang mengapa dan mencakup bagaimana. Tetapi untuk memasukkannya ke dalam beberapa baris 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'
);

Masalah dengan pendekatan ini bagaimanapun, adalah bahwa itu akan membuat ulang objek setiap kali Anda membuatnya. Pendekatan lain adalah mendeklarasikan objek Anda pada tumpukan prototipe, seperti ini:

// 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');

Ada sedikit kerugian dalam hal introspeksi. Dumping testOne, akan menghasilkan informasi yang kurang berguna. Juga properti pribadi "privateVariable" di "testOne" dibagikan di semua contoh, juga disebutkan dengan sangat membantu dalam balasan oleh shesek.


3
Perhatikan bahwa di testOne privateVariablehanyalah variabel dalam cakupan IIFE , dan dibagikan di semua instance, jadi Anda tidak boleh menyimpan data khusus instance di dalamnya. (di testTwo itu khusus-instance, karena setiap panggilan ke testTwo () membuat cakupan baru, per-instance)
shesek

Saya memberi suara positif karena Anda menunjukkan pendekatan lain dan mengapa tidak menggunakannya karena itu membuat salinan
Murphy316

Masalah membuat ulang objek setiap saat terutama karena metode yang dibuat ulang untuk setiap objek baru. Namun, kami dapat mengurangi masalah dengan mendefinisikan metode di Dog.prototype. Jadi daripada menggunakan this.bark = function () {...}, kita bisa melakukan di Dot.prototype.bark = function () {...}luar Dogfungsi. (Lihat detail lebih lanjut dalam jawaban ini )
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
Mungkin menambahkan tautan ini dengan jawaban Anda mungkin melengkapi gambaran tersebut lebih lanjut: developer.mozilla.org/en/docs/Web/JavaScript/Reference/…
Dynom

14

Saya akan melihat di YUI , dan di Baseperpustakaan Dean Edward : http://dean.edwards.name/weblog/2006/03/base/

Untuk YUI, Anda dapat melihat sekilas modul lang , esp. yang YAHOO.lang.extend metode. Kemudian, Anda dapat menjelajahi sumber beberapa widget atau utilitas dan melihat bagaimana mereka menggunakan metode itu.


YUI 2 sudah tidak digunakan lagi mulai tahun 2011, sehingga tautan ke langsetengah rusak. Adakah yang peduli untuk memperbaikinya untuk YUI 3?
ack

lang in yui 3 sepertinya tidak memiliki metode perluasan. tetapi karena jawabannya bermaksud menggunakan implementasi sebagai contoh, versi tidak menjadi masalah.
eMBee


5

Ini adalah contoh paling jelas yang saya temukan, dari buku Node Mixu ( http://book.mixu.net/node/ch6.html ):

Saya lebih menyukai komposisi daripada warisan:

Komposisi - Fungsionalitas suatu objek terdiri dari kumpulan kelas yang berbeda dengan memuat instance objek lain. Warisan - Fungsionalitas suatu objek terdiri dari fungsionalitasnya sendiri ditambah fungsionalitas dari kelas induknya. Jika Anda harus memiliki warisan, gunakan JS lama biasa

Jika Anda harus mengimplementasikan inheritance, setidaknya hindari menggunakan fungsi implementasi / sihir tidak standar lainnya. Berikut adalah cara menerapkan faksimili pewarisan yang wajar dalam ES3 murni (selama Anda mengikuti aturan tidak pernah menentukan properti pada prototipe):

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();

Ini tidak sama dengan pewarisan klasik - tetapi Javascript standar dan dapat dimengerti dan memiliki fungsionalitas yang paling banyak dicari orang: konstruktor yang dapat dirantai dan kemampuan untuk memanggil metode superclass.


4

ES6 classdanextends

ES6 classdan extendshanya gula sintaks untuk kemungkinan manipulasi rantai prototipe sebelumnya, dan bisa dibilang pengaturan yang paling kanonik.

Pertama pelajari lebih lanjut tentang rantai prototipe dan .pencarian properti di: https://stackoverflow.com/a/23877420/895245

Sekarang mari kita dekonstruksi apa yang terjadi:

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

Diagram yang disederhanakan tanpa semua objek yang telah ditentukan sebelumnya:

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


1

Contoh terbaik yang pernah saya lihat ada di JavaScript Douglas Crockford : The Good Parts . Pasti layak dibeli untuk membantu Anda mendapatkan pandangan yang seimbang tentang bahasa tersebut.

Douglas Crockford bertanggung jawab atas format JSON dan bekerja di Yahoo sebagai guru JavaScript.


7
bertanggung jawab? kedengarannya hampir seperti "bersalah atas" :)
Roland Bouman

@Roland Saya pikir JSON adalah format non-verbose yang cukup bagus untuk menyimpan data. Dia jelas tidak menemukannya, formatnya ada di sana untuk pengaturan konfigurasi di Steam pada tahun 2002
Chris S

Chris S, saya rasa juga begitu - Semakin sering saya berharap kita semua bisa melewatkan XML sebagai format pertukaran dan langsung pindah ke JSON.
Roland Bouman

3
Tidak banyak yang bisa ditemukan: JSON adalah bagian dari sintaks literal objek JavaScript sendiri, yang telah ada dalam bahasa ini sejak sekitar 1997.
Tim Down

@Titik waktu yang baik - Saya tidak menyadarinya sejak awal
Chris S


0

Menambahkan contoh pewarisan berbasis Prototipe di 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 menggunakan implementasi pewarisan yang jauh lebih mudah dengan penggunaan kata kunci konstruktor dan super.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.