JavaScript OOP di NodeJS: bagaimana?

118

Saya sudah terbiasa dengan OOP klasik seperti di Jawa.

Apa praktik terbaik untuk melakukan OOP di JavaScript menggunakan NodeJS?

Setiap Kelas adalah file dengan module.export?

Bagaimana cara membuat Kelas?

this.Class = function() {
    //constructor?
    var privateField = ""
    this.publicField = ""
    var privateMethod = function() {}
    this.publicMethod = function() {} 
}

vs. (Saya bahkan tidak yakin itu benar)

this.Class = {
    privateField: ""
    , privateMethod: function() {}

    , return {
        publicField: ""
        publicMethod: function() {}
    }
}

vs.

this.Class = function() {}

this.Class.prototype.method = function(){}

...

Bagaimana cara kerja warisan?

Apakah ada modul khusus untuk mengimplementasikan OOP di NodeJS?

Saya menemukan ribuan cara berbeda untuk membuat sesuatu yang menyerupai OOP .. tapi saya tidak tahu cara apa yang paling banyak digunakan / praktis / bersih.

Pertanyaan bonus : apa "gaya OOP" yang disarankan untuk digunakan dengan MongooseJS? (dapatkah dokumen MongooseJS dilihat sebagai Kelas dan model digunakan sebagai instance?)

EDIT

berikut adalah contoh di JsFiddle tolong berikan umpan balik.

//http://javascriptissexy.com/oop-in-javascript-what-you-need-to-know/
function inheritPrototype(childObject, parentObject) {
    var copyOfParent = Object.create(parentObject.prototype)
    copyOfParent.constructor = childObject
    childObject.prototype = copyOfParent
}

//example
function Canvas (id) {
    this.id = id
    this.shapes = {} //instead of array?
    console.log("Canvas constructor called "+id)
}
Canvas.prototype = {
    constructor: Canvas
    , getId: function() {
        return this.id
    }
    , getShape: function(shapeId) {
        return this.shapes[shapeId]
    }
    , getShapes: function() {
        return this.shapes
    }
    , addShape: function (shape)  {
        this.shapes[shape.getId()] = shape
    }
    , removeShape: function (shapeId)  {
        var shape = this.shapes[shapeId]
        if (shape)
            delete this.shapes[shapeId]
        return shape
    }
}

function Shape(id) {
    this.id = id
    this.size = { width: 0, height: 0 }
    console.log("Shape constructor called "+id)
}
Shape.prototype = {
    constructor: Shape
    , getId: function() {
        return this.id
    }
    , getSize: function() {
        return this.size
    }
    , setSize: function (size)  {
        this.size = size
    }
}

//inheritance
function Square(id, otherSuff) {
    Shape.call(this, id) //same as Shape.prototype.constructor.apply( this, arguments ); ?
    this.stuff = otherSuff
    console.log("Square constructor called "+id)
}
inheritPrototype(Square, Shape)
Square.prototype.getSize = function() { //override
    return this.size.width
}

function ComplexShape(id) {
    Shape.call(this, id)
    this.frame = null
    console.log("ComplexShape constructor called "+id)
}
inheritPrototype(ComplexShape, Shape)
ComplexShape.prototype.getFrame = function() {
    return this.frame
}
ComplexShape.prototype.setFrame = function(frame) {
    this.frame = frame
}

function Frame(id) {
    this.id = id
    this.length = 0
}
Frame.prototype = {
    constructor: Frame
    , getId: function() {
        return this.id
    }
    , getLength: function() {
        return this.length
    }
    , setLength: function (length)  {
        this.length = length
    }
}

/////run
var aCanvas = new Canvas("c1")
var anotherCanvas = new Canvas("c2")
console.log("aCanvas: "+ aCanvas.getId())

var aSquare = new Square("s1", {})
aSquare.setSize({ width: 100, height: 100})
console.log("square overridden size: "+aSquare.getSize())

var aComplexShape = new ComplexShape("supercomplex")
var aFrame = new Frame("f1")
aComplexShape.setFrame(aFrame)
console.log(aComplexShape.getFrame())

aCanvas.addShape(aSquare)
aCanvas.addShape(aComplexShape)
console.log("Shapes in aCanvas: "+Object.keys(aCanvas.getShapes()).length)

anotherCanvas.addShape(aCanvas.removeShape("supercomplex"))
console.log("Shapes in aCanvas: "+Object.keys(aCanvas.getShapes()).length)
console.log("Shapes in anotherCanvas: "+Object.keys(anotherCanvas.getShapes()).length)

console.log(aSquare instanceof Shape)
console.log(aComplexShape instanceof Shape)
fusio
sumber
12
Tidak ada yang benar - benar spesifik tentang OO JS di node.js. Hanya ada OO JS. Pertanyaan Anda adalah tentang menerjemahkan teknik Java OOP ke JS, yang sebenarnya kurang tepat . Saya pikir lebih baik Anda menghabiskan waktu / energi yang sama untuk mempelajari cara kerja model berbasis prototipe JS, dan bagaimana Anda dapat menggunakannya untuk keuntungan Anda
Elias Van Ootegem
1
Selain itu, Anda tidak memiliki kelas di JavaScript. Anda dapat membuat perilaku seperti kelas dengan fungsi, tetapi secara umum itu bukan ide yang bagus.
m_vdbeek
1
@AwakeZoldiek Apa yang Anda maksud dengan itu bukan "fitur asli"?
Esailija
4
@fusio Dengan pewarisan prototypal secara umum, objek / instance mewarisi dari objek / instance lain. Jadi, kelas tidak digunakan karena Anda tidak bekerja dengan definisi abstrak. Jadi, pewarisan dilakukan melalui sebuah prototyperantai . Dan, tidak, objek tidak mendukung anggota " pribadi ". Hanya closure yang bisa menawarkan itu, meskipun modul / skrip di Node.js diimplementasikan sebagai closure.
Jonathan Lonowski
1
@Esailija Sebenarnya saya tidak bermaksud menyarankan agar penutupan dapat membuat anggota pribadi. Hanya menyarankan bahwa closure dan variabel terlampir sedekat yang bisa Anda dapatkan di JavaScript. Tapi, untuk bagian lain: satu-satunya " implementasi " yang saya sebutkan terkait modul Node, yang dievaluasi dalam closure di mana beberapa global didefinisikan unik untuk setiap skrip.
Jonathan Lonowski

Jawaban:

116

Ini adalah contoh yang berhasil di luar kotak. Jika Anda ingin lebih sedikit "hacky", Anda harus menggunakan perpustakaan warisan atau semacamnya.

Nah di file animal.js Anda akan menulis:

var method = Animal.prototype;

function Animal(age) {
    this._age = age;
}

method.getAge = function() {
    return this._age;
};

module.exports = Animal;

Untuk menggunakannya di file lain:

var Animal = require("./animal.js");

var john = new Animal(3);

Jika Anda menginginkan "subkelas" maka di dalam mouse.js:

var _super = require("./animal.js").prototype,
    method = Mouse.prototype = Object.create( _super );

method.constructor = Mouse;

function Mouse() {
    _super.constructor.apply( this, arguments );
}
//Pointless override to show super calls
//note that for performance (e.g. inlining the below is impossible)
//you should do
//method.$getAge = _super.getAge;
//and then use this.$getAge() instead of super()
method.getAge = function() {
    return _super.getAge.call(this);
};

module.exports = Mouse;

Anda juga dapat mempertimbangkan "Peminjaman metode" alih-alih warisan vertikal. Anda tidak perlu mewarisi dari "kelas" untuk menggunakan metodenya di kelas Anda. Misalnya:

 var method = List.prototype;
 function List() {

 }

 method.add = Array.prototype.push;

 ...

 var a = new List();
 a.add(3);
 console.log(a[0]) //3;
Esailija
sumber
apa perbedaan antara menggunakan Animal.prototype.getAge= function(){}dan menambahkan this.getAge = function(){}di dalam function Animal() {}? Sub-kelas tampaknya agak hacky .. dengan pustaka "warisan" yang Anda maksud seperti yang inheritsdisarankan oleh @badsyntax?
fusio
4
@fusio ya, Anda bisa melakukan hal seperti inherits(Mouse, Animal);itu membersihkan warisan set up sedikit. Perbedaannya adalah Anda membuat identitas fungsi baru untuk setiap objek yang dibuat alih-alih berbagi satu fungsi. Jika Anda memiliki 10 mouse, Anda telah membuat 10 identitas fungsi (itu hanya karena mouse memiliki satu metode, jika memiliki 10 metode, 10 mouse akan membuat 100 identitas fungsi, server Anda akan dengan cepat membuang sebagian besar CPU-nya pada GC: P) , meskipun Anda tidak akan menggunakannya untuk apa pun. Bahasa tidak memiliki kekuatan ekspresif yang cukup untuk mengoptimalkannya saat ini.
Esailija
Wow. Terima kasih :) Ini sepertinya cukup sederhana, saya juga menemukan Details_of_the_Object_Model di mana mereka membandingkan JS dengan Java. Namun, untuk mewarisi mereka cukup melakukan: Mouse.prototype = new Animal().. bagaimana cara membandingkannya dengan contoh Anda? (misalnya apa Object.create()?)
fusio
@fusio Object.create tidak memanggil konstruktor ... jika konstruktor memiliki efek samping atau semacamnya (ia dapat melakukan apa saja yang dapat dilakukan oleh fungsi normal, tidak seperti di Java), maka memanggilnya tidak diinginkan saat menyiapkan rantai pewarisan.
Esailija
3
Saya masih mempertahankannya untuk seseorang yang mulai menggunakan JavaScript, meretas solusi seperti ini bukanlah solusi yang baik. Ada begitu banyak kebiasaan dan jebakan yang tidak mudah di-debug sehingga hal ini tidak disarankan.
m_vdbeek
43

Karena komunitas Node.js memastikan fitur baru dari spesifikasi JavaScript ECMA-262 dibawa ke pengembang Node.js secara tepat waktu.

Anda dapat melihat kelas JavaScript . Link MDN ke kelas JS Dalam ECMAScript 6 kelas JavaScript diperkenalkan, metode ini menyediakan cara yang lebih mudah untuk memodelkan konsep OOP dalam Javascript.

Catatan : Kelas JS hanya akan berfungsi dalam mode ketat .

Di bawah ini adalah beberapa kerangka kelas, warisan ditulis dalam Node.js (Digunakan Node.js Versi v5.0.0 )

Deklarasi kelas:

'use strict'; 
class Animal{

 constructor(name){
    this.name = name ;
 }

 print(){
    console.log('Name is :'+ this.name);
 }
}

var a1 = new Animal('Dog');

Warisan :

'use strict';
class Base{

 constructor(){
 }
 // methods definitions go here
}

class Child extends Base{
 // methods definitions go here
 print(){ 
 }
}

var childObj = new Child();
Piyush Sagar
sumber
14

Saya menyarankan untuk menggunakan inheritshelper yang disertakan dengan utilmodul standar : http://nodejs.org/api/util.html#util_util_inherits_constructor_superconstructor

Ada contoh bagaimana menggunakannya di halaman tertaut.

badsyntax
sumber
Ini adalah jawaban yang paling berguna sehubungan dengan lingkungan inti NodeJS.
Philzen
1
Tampilan sekarang sudah usang. Dari tautan jawaban: Catatan: penggunaan util.inherits () tidak disarankan. Silakan gunakan kelas ES6 dan perluas kata kunci untuk mendapatkan dukungan pewarisan tingkat bahasa. Perhatikan juga bahwa kedua gaya tersebut tidak kompatibel secara semantik.
Frosty Z
11

Ini adalah video terbaik tentang JavaScript Berorientasi Objek di internet:

Panduan Definitif untuk JavaScript Berorientasi Objek

Tonton dari awal hingga akhir !!

Pada dasarnya Javascript adalah bahasa berbasis Prototype yang cukup berbeda dengan kelas-kelas di Java, C ++, C #, dan teman-teman populer lainnya. Video tersebut menjelaskan konsep inti jauh lebih baik daripada jawaban apa pun di sini.

Dengan ES6 (dirilis 2015) kami mendapat kata kunci "class" yang memungkinkan kami menggunakan "class" Javascript seperti yang kami lakukan dengan Java, C ++, C #, Swift, dll.

Tangkapan layar dari video yang menunjukkan cara menulis dan membuat instance kelas / subkelas Javascript: masukkan deskripsi gambar di sini

etayluz
sumber
Saya menghargai Anda telah memberikan jawaban untuk ES6. Terima kasih! Sayangnya, saya tidak memiliki data untuk menonton video 27 menit. Saya akan melanjutkan pencarian saya untuk panduan tertulis.
tim.rohrer
Terima kasih untuk videonya. Saya membantu saya untuk menghapus banyak pertanyaan yang saya miliki tentang javascript.
Kishore Devaraj
4

Dalam komunitas Javascript, banyak orang berpendapat bahwa OOP tidak boleh digunakan karena model prototipe tidak memungkinkan untuk melakukan OOP yang ketat dan kuat secara native. Namun, menurut saya OOP bukan masalah bahasa, melainkan masalah arsitektur.

Jika Anda ingin menggunakan OOP yang sangat kuat di Javascript / Node, Anda dapat melihat kerangka kerja sumber terbuka full-stack Danf . Ini menyediakan semua fitur yang diperlukan untuk kode OOP yang kuat (kelas, antarmuka, warisan, injeksi ketergantungan, ...). Ini juga memungkinkan Anda untuk menggunakan kelas yang sama di kedua sisi server (node) dan klien (browser). Selain itu, Anda dapat membuat kode modul danf Anda sendiri dan membaginya dengan siapa saja berkat Npm.

Gnucki
sumber
-1

Jika Anda bekerja sendiri, dan menginginkan OOP yang paling mendekati seperti yang Anda temukan di Java atau C # atau C ++, lihat pustaka javascript, CrxOop. CrxOop menyediakan sintaks yang cukup familiar bagi pengembang Java.

Hati-hati, OOP Java tidak sama dengan yang ditemukan di Javascript. Untuk mendapatkan perilaku yang sama seperti di Java, gunakan kelas CrxOop, bukan struktur CrxOop, dan pastikan semua metode Anda virtual. Contoh sintaksnya adalah,

crx_registerClass("ExampleClass", 
{ 
    "VERBOSE": 1, 

    "public var publicVar": 5, 
    "private var privateVar": 7, 

    "public virtual function publicVirtualFunction": function(x) 
    { 
        this.publicVar1 = x;
        console.log("publicVirtualFunction"); 
    }, 

    "private virtual function privatePureVirtualFunction": 0, 

    "protected virtual final function protectedVirtualFinalFunction": function() 
    { 
        console.log("protectedVirtualFinalFunction"); 
    }
}); 

crx_registerClass("ExampleSubClass", 
{ 
    VERBOSE: 1, 
    EXTENDS: "ExampleClass", 

    "public var publicVar": 2, 

    "private virtual function privatePureVirtualFunction": function(x) 
    { 
        this.PARENT.CONSTRUCT(pA);
        console.log("ExampleSubClass::privatePureVirtualFunction"); 
    } 
}); 

var gExampleSubClass = crx_new("ExampleSubClass", 4);

console.log(gExampleSubClass.publicVar);
console.log(gExampleSubClass.CAST("ExampleClass").publicVar);

Kode ini murni javascript, tidak transpiling. Contoh tersebut diambil dari sejumlah contoh dari dokumentasi resmi.

ua692875
sumber