ООП JavaScript в NodeJS: как?

118

Я привык к классическому ООП, как в Java.

Каковы лучшие практики для ООП в 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() {}
    }
}

против

this.Class = function() {}

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

...

Как будет работать наследование?

Существуют ли специальные модули для реализации ООП в NodeJS?

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

Дополнительный вопрос : какой предлагаемый «стиль ООП» использовать с MongooseJS? (можно ли рассматривать документ MongooseJS как класс, а модель как экземпляр?)

РЕДАКТИРОВАТЬ

вот пример в 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)
Fusio
источник
12
Нет ничего особенного в OO JS в node.js. Есть просто OO JS. Ваш вопрос касается перевода методов Java ООП на JS, что просто неправильно . Думаю, лучше потратить
столько
1
Кроме того, у вас нет классов в JavaScript. Вы можете создать поведение, подобное классу, с помощью функций, но обычно это не очень хорошая идея.
m_vdbeek
1
@AwakeZoldiek Что вы имеете в виду, говоря, что это не "нативная функция"?
Esailija
4
@fusio В целом при прототипном наследовании объекты / экземпляры наследуются от других объектов / экземпляров. Итак, классы не используются, потому что вы не работаете с абстрактными определениями. Итак, наследование осуществляется по prototypeцепочке . И нет, объект не поддерживает " закрытые " члены. Это могут предложить только замыкания , хотя модули / скрипты в Node.js реализованы как замыкания.
Джонатан Лоновски,
1
@Esailija На самом деле я не имел в виду, что закрытие может создавать частных участников. Просто предполагал, что замыкания и вложенные переменные настолько близки, насколько это возможно в JavaScript. Но с другой стороны: единственная « реализация », которую я упомянул, относилась к модулям Node, которые оцениваются внутри замыкания, где некоторые глобальные переменные определены уникальными для каждого скрипта.
Джонатан Лоновски,

Ответы:

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;
Esailija
источник
в чем разница между использованием Animal.prototype.getAge= function(){}и простым добавлением this.getAge = function(){}внутрь function Animal() {}? inheritsПодкласс кажется немного взломанным ... с библиотекой "наследования" вы имеете в виду что-то вроде того, что предлагает @badsyntax?
fusio
4
@fusio да, вы можете сделать что-то подобное, inherits(Mouse, Animal);чтобы немного очистить настройку наследования. Разница в том, что вы создаете новый идентификатор функции для каждого экземпляра объекта вместо совместного использования одной функции. Если у вас 10 мышей, вы создали 10 идентификаторов функций (это только потому, что у мыши есть один метод, если бы у него было 10 методов, 10 мышей создали бы 100 идентификаторов функций, ваш сервер быстро потратил бы большую часть своего процессора на GC: P) , даже если вы ни для чего не будете их использовать. У языка недостаточно выразительной силы, чтобы оптимизировать это в настоящее время.
Esailija
Ого. Спасибо :) Это кажется довольно простым, я также нашел 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 В классах JavaScript ECMAScript 6 этот метод обеспечивает более простой способ моделирования концепций ООП в 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

Вот пример того, как использовать его на связанной странице.

badsyntax
источник
Это самый полезный ответ относительно основной среды NodeJS.
Philzen
1
Сейчас выглядит устаревшим. Из ссылки для ответа: Примечание: использование util.inherits () не рекомендуется. Используйте класс ES6 и ключевые слова extends, чтобы получить поддержку наследования на уровне языка. Также обратите внимание, что эти два стиля семантически несовместимы.
Frosty Z
11

Это лучший видеоролик об объектно-ориентированном JavaScript в Интернете:

Полное руководство по объектно-ориентированному JavaScript

Смотрите от начала до конца !!

По сути, Javascript - это язык, основанный на прототипах, который сильно отличается от классов в Java, C ++, C # и других популярных языках. Видео объясняет основные концепции намного лучше, чем любой ответ здесь.

В ES6 (выпущенном в 2015 г.) мы получили ключевое слово «class», которое позволяет нам использовать «классы» Javascript, как в случае с Java, C ++, C #, Swift и т. Д.

Снимок экрана из видео, показывающий, как написать и создать экземпляр класса / подкласса Javascript: введите описание изображения здесь

etayluz
источник
Я благодарен вам за ответ по ES6. Спасибо! К сожалению, у меня нет данных для просмотра 27-минутного видео. Я продолжу поиск письменных указаний.
tim.rohrer
Спасибо за видео. Я помог мне ответить на многие вопросы, которые у меня возникли по поводу javascript.
Кишор Деварадж
4

В сообществе Javascript многие люди утверждают, что ООП не следует использовать, потому что модель прототипа не позволяет выполнять строгое и надежное ООП изначально. Однако я не думаю, что ООП - это вопрос языка, а скорее вопрос архитектуры.

Если вы хотите использовать действительно сильное ООП в Javascript / Node, вы можете взглянуть на полнофункциональный фреймворк с открытым исходным кодом Danf . Он предоставляет все необходимые функции для сильного ООП-кода (классы, интерфейсы, наследование, внедрение зависимостей, ...). Он также позволяет использовать одни и те же классы как на стороне сервера (узла), так и на стороне клиента (браузера). Более того, вы можете кодировать свои собственные модули danf и делиться ими с кем угодно благодаря Npm.

Gnucki
источник
-1

Если вы работаете самостоятельно и хотите, чтобы ООП было максимально приближено к Java, C # или C ++, см. Библиотеку javascript CrxOop. CrxOop предоставляет синтаксис, несколько знакомый разработчикам Java.

Только будьте осторожны, ООП в 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, без транспиляции. Пример взят из ряда примеров из официальной документации.

ua692875
источник