JavaScript OOP в NodeJS: як?


118

Я звик до класичного OOP, як на Java.

Які найкращі практики робити OOP в JavaScript за допомогою NodeJS?

Кожен клас - це файл із module.export?

Як створити класи?

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

vs. (я навіть не впевнений, що це правильно)

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

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

vs.

this.Class = function() {}

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

...

Як би працювала спадщина?

Чи є конкретні модулі для впровадження OOP в NodeJS?

Я знаходжу тисячу різних способів створити речі, що нагадують ООП .. але я не маю поняття, що є найбільш використовуваним / практичним / чистим способом.

Питання про бонус : який запропонований "стиль OOP" використовувати для MongooseJS? (чи може документ MongooseJS розглядатися як клас та модель, яка використовується як екземпляр?)

EDIT

ось приклад в JsFiddle, будь ласка, надайте відгуки.

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

12
Про OO JS в node.js немає нічого конкретного. Є просто ОО JS. Ваше питання стосується перекладу технік Java OOP в JS, що просто не правильно . Я думаю, що краще ви витратили той же час / енергію, щоб дізнатися, як працює модель прототипу JS, і як ви можете використовувати її на вашу користь
Elias Van Ootegem

1
Крім того, у вас немає класів JavaScript. Ви можете створювати поведінку, схожу на клас, за допомогою функцій, але це, як правило, не дуже добре.
m_vdbeek

1
@AwakeZoldiek Що ти маєш на увазі, що це не "рідна особливість"?
Esailija

4
@fusio Взагалі з прототиповим успадкуванням об'єкти / екземпляри успадковують від інших об'єктів / екземплярів. Отже, класи не використовуються, оскільки ви не працюєте з абстрактними визначеннями. Отже, успадкування здійснюється через prototypeланцюжок . І, ні, об'єкт не підтримує " приватних " членів. Лише закриття можуть запропонувати це, хоча модулі / скрипти в Node.js реалізовані як закриття.
Джонатан Лоновський

1
@Esailija Я насправді не мав на увазі запропонувати закриття можуть створювати приватних членів. Якраз підказував, що закриття та вкладені змінні настільки ж близькі, як ви можете отримати в JavaScript. Але, з іншого боку: єдиною " реалізацією " я згадав модулі "Вузол", які оцінюються в межах закриття, де деякі глобальні точки визначаються унікальними для кожного сценарію.
Джонатан Лоновський

Відповіді:


116

Це приклад, який працює нестандартно. Якщо ви хочете менше "хакі", вам слід скористатися спадковою бібліотекою чи подібною.

Ну у файл animal.js ви б написали:

var method = Animal.prototype;

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

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

module.exports = Animal;

Щоб використовувати його в іншому файлі:

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

var john = new Animal(3);

Якщо ви хочете "підклас", то всередині 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;

Також ви можете розглянути "Метод запозичення" замість вертикального успадкування. Вам не потрібно успадковувати "клас", щоб використовувати його метод у своєму класі. Наприклад:

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

 }

 method.add = Array.prototype.push;

 ...

 var a = new List();
 a.add(3);
 console.log(a[0]) //3;

в чому різниця між використанням Animal.prototype.getAge= function(){}та просто додаванням this.getAge = function(){}всередину function Animal() {}? inheritsПідклас здається трохи хитким. З бібліотекою "спадкування" ви маєте на увазі щось на зразок запропонованого @badsyntax?
fusio

4
@fusio так, ви можете зробити щось подібне, inherits(Mouse, Animal);щоб очистити встановлену спадщину. Різниця полягає в тому, що ви створюєте нову ідентичність функції для кожного створеного об'єкта замість того, щоб ділити одну функцію. Якщо у вас є 10 мишей, ви створили 10 ідентифікацій функцій (це лише тому, що у миші є один метод, якби у нього було 10 методів, 10 мишей створили б 100 ідентифікацій функцій, ваш сервер швидко витратить більшу частину свого процесора на GC: P) , незважаючи на те, що ви нічого не будете використовувати. Мова не має достатньо виразної сили, щоб оптимізувати це в даний момент.
Есаїлія

Вуа. Дякую :) Це здається досить простим, я також знайшов Details_of_the_Object_Model, де вони порівнюють JS з Java. І все-таки для їх успадкування вони просто роблять: Mouse.prototype = new Animal().. як це порівняти з вашим прикладом? (наприклад, що є Object.create()?)
fusio

@fusio Object.create не викликає конструктора ... якщо у конструктора є побічні ефекти або подібні (він може робити все, що може зробити нормальна функція, на відміну від Java), то викликати його небажано при налаштуванні ланцюжка спадкування.
Esailija

3
Я все ще стверджую, що для когось, хто починає використовувати JavaScript, зламати таке рішення, як це, не є хорошим рішенням. Існує так багато химерностей і підводних каменів, які непросто налагодити, що цього не варто радити.
m_vdbeek

43

Оскільки спільнота Node.js забезпечує нові можливості із специфікації JavaScript ECMA-262, своєчасно передається розробникам Node.js.

Ви можете подивитися на JavaScript-класи . Посилання MDN до класів JS У ECMAScript 6 введені класи JavaScript, цей метод забезпечує простіший спосіб моделювання концепцій OOP у Javascript.

Примітка : Класи JS працюватимуть лише в суворому режимі .

Нижче наведено кістяк класу, спадкування, записане в Node.js (Використовувана версія Node.js v5.0.0 )

Декларації класу:

'use strict'; 
class Animal{

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

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

var a1 = new Animal('Dog');

Спадщина:

'use strict';
class Base{

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

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

var childObj = new Child();

14

Я пропоную використовувати inheritsпомічник, що постачається зі стандартним utilмодулем: http://nodejs.org/api/util.html#util_util_inherits_constructor_superconstructor

На прикладі пов’язаної сторінки є приклад того, як його використовувати.


Це найкорисніша відповідь щодо основного середовища NodeJS.
Фільцен

1
Зараз вигляд застарілий. З посилання відповіді: Примітка: використання util.inherits () не рекомендується. Будь ласка, використовуйте клас ES6 і розширює ключові слова, щоб отримати підтримку успадкування рівня мови. Також зауважте, що два стилі семантично несумісні.
Frosty Z

11

Це найкраще відео про об’єктно-орієнтований JavaScript в Інтернеті:

Поточний посібник по об'єктно-орієнтованому JavaScript

Дивитися від початку до кінця !!

В основному, Javascript заснований на прототипі мова, яка зовсім інша, ніж класи Java, C ++, C # та інших популярних друзів. Відео пояснює основні поняття набагато краще, ніж будь-яка відповідь тут.

За допомогою ES6 (випущений 2015 р.) Ми отримали ключове слово "class", яке дозволяє нам використовувати "класи" Javascript, як і в Java, C ++, C #, Swift тощо.

Знімок екрана із відео, що показує, як писати та інстанціювати клас / підклас Javascript: введіть тут опис зображення


Дякую вам, що ви дали відповідь для ES6. Дякую! На жаль, у мене немає даних для перегляду 27-хвилинного відео. Я продовжуватиму пошук письмових вказівок.
tim.rohrer

Дякуємо за відео. Я допоміг мені очистити багато питань, які виникли у мене щодо JavaScript.
Кішор Деварадж

4

У спільноті Javascript багато людей стверджують, що OOP не слід використовувати, тому що модель-прототип не дозволяє робити суворий та надійний OOP на самому світі. Однак я не вважаю, що OOP - це питання, а не питання архітектури.

Якщо ви хочете використовувати справжній сильний OOP в Javascript / Node, ви можете ознайомитися з повноцінним фреймворком з відкритим кодом Danf . Він надає всі необхідні функції для сильного коду OOP (класи, інтерфейси, успадкування, залежність-введення, ...). Це також дозволяє використовувати однакові класи як на стороні сервера (вузла), так і клієнта (браузера). Більше того, ви можете кодувати свої власні модулі danf та ділитися ними з ким-небудь завдяки Npm.


-1

Якщо ви працюєте самостійно і хочете, щоб OOP наблизився так, як ви знайдете в Java або C # або C ++, перегляньте бібліотеку javascript, CrxOop. CrxOop надає синтаксис, дещо знайомий розробникам Java.

Будьте обережні, OOP Java не такий, як у Javascript. Щоб отримати таку поведінку, як у Java, використовуйте класи CrxOop, а не структури CrxOop, і переконайтесь, що всі ваші методи віртуальні. Прикладом синтаксису є:

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

Код - це чистий JavaScript, без трансляції. Приклад взято з ряду прикладів з офіційної документації.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.