JavaScript OOP trong NodeJS: làm thế nào?


118

Tôi đã quen với OOP cổ điển như trong Java.

Các phương pháp hay nhất để thực hiện OOP trong JavaScript bằng NodeJS là gì?

Mỗi Lớp là một tệp với module.export?

Làm thế nào để tạo Lớp học?

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

so với (Tôi thậm chí không chắc nó là chính xác)

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

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

vs.

this.Class = function() {}

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

...

Kế thừa sẽ hoạt động như thế nào?

Có các mô-đun cụ thể để triển khai OOP trong NodeJS không?

Tôi đang tìm hàng nghìn cách khác nhau để tạo ra những thứ giống với OOP .. nhưng tôi không biết đâu là cách được sử dụng nhiều nhất / thực tế / sạch sẽ.

Câu hỏi bổ sung : "Kiểu OOP" được đề xuất để sử dụng với MongooseJS là gì? (một tài liệu MongooseJS có thể được xem như là một Lớp và một mô hình được sử dụng như một thể hiện không?)

BIÊN TẬP

đây là một ví dụ trong JsFiddle vui lòng cung cấp phản hồi.

//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
Không có gì thực sự cụ thể về OO JS trong node.js. Chỉ có OO JS. Câu hỏi của bạn là về việc dịch các kỹ thuật Java OOP sang JS, điều này không đúng . Tôi nghĩ rằng nó tốt hơn bạn dành thời / năng lượng trong việc học như thế nào JS của nguyên mẫu dựa trên tác phẩm mô hình, và làm thế nào bạn có thể sử dụng nó để lợi thế của bạn
Elias Văn Ootegem

1
Ngoài ra, bạn không có các lớp học trong JavaScript. Bạn có thể tạo hành vi giống như lớp với các hàm, nhưng nói chung không phải là một ý kiến ​​hay.
m_vdbeek

1
@AwakeZoldiek Ý bạn là gì khi nó không phải là "tính năng gốc"?
Esailija

4
@fusio Với kế thừa nguyên mẫu nói chung, các đối tượng / cá thể kế thừa từ các đối tượng / cá thể khác. Vì vậy, các lớp không được sử dụng bởi vì bạn không làm việc với các định nghĩa trừu tượng. Vì vậy, kế thừa được thực hiện thông qua một prototypechuỗi . Và, không, đối tượng không hỗ trợ các thành viên " riêng tư ". Chỉ các bao đóng mới có thể cung cấp điều đó, mặc dù các mô-đun / tập lệnh trong Node.js được triển khai dưới dạng các bao đóng.
Jonathan Lonowski

1
@Esailija Tôi thực sự không có ý đề xuất việc đóng cửa có thể tạo thành viên riêng tư. Chỉ gợi ý rằng các bao đóng và các biến kèm theo gần như bạn có thể nhận được trong JavaScript. Nhưng, đối với phần khác: chỉ " thực hiện " tôi đã đề cập coi module Node, được đánh giá trong một đóng cửa nơi một số các globals được định nghĩa duy nhất cho mỗi kịch bản.
Jonathan Lonowski

Câu trả lời:


116

Đây là một ví dụ hoạt động hiệu quả. Nếu bạn muốn ít "hacky" hơn, bạn nên sử dụng thư viện kế thừa hoặc tương tự.

Trong một tệp tin thú vị, bạn sẽ viết:

var method = Animal.prototype;

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

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

module.exports = Animal;

Để sử dụng nó trong tệp khác:

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

var john = new Animal(3);

Nếu bạn muốn có một "lớp con" thì bên trong 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;

Ngoài ra bạn có thể xem xét "Phương pháp vay mượn" thay vì kế thừa theo chiều dọc. Bạn không cần phải kế thừa từ một "lớp" để sử dụng phương thức của nó trên lớp của bạn. Ví dụ:

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

 }

 method.add = Array.prototype.push;

 ...

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

sự khác biệt giữa việc sử dụng Animal.prototype.getAge= function(){}và chỉ cần thêm vào this.getAge = function(){}bên trong là function Animal() {}gì? Lớp con có vẻ hơi hacky .. với thư viện "kế thừa", bạn có nghĩa là một cái gì đó như được inheritsđề xuất bởi @badsyntax?
fusio

4
@fusio vâng, bạn có thể làm điều gì đó như inherits(Mouse, Animal);vậy để dọn dẹp phần thừa kế đã thiết lập một chút. Sự khác biệt là bạn đang tạo nhận dạng hàm mới cho từng đối tượng được khởi tạo thay vì chia sẻ một hàm. Nếu bạn có 10 con chuột, bạn đã tạo ra 10 nhận dạng chức năng (đó chỉ là vì chuột có một phương thức, nếu nó có 10 phương thức, 10 con chuột sẽ tạo ra 100 nhận dạng chức năng, máy chủ của bạn sẽ nhanh chóng lãng phí hầu hết CPU của nó trên GC: P) , mặc dù bạn sẽ không sử dụng chúng cho bất cứ việc gì. Hiện tại, ngôn ngữ không có đủ sức mạnh biểu đạt để tối ưu hóa điều này.
Esailija

Chà! Cảm ơn :) Điều này có vẻ khá đơn giản, tôi cũng tìm thấy Details_of_the_Object_Model nơi họ so sánh JS với Java. Tuy nhiên, để kế thừa họ chỉ cần làm: Mouse.prototype = new Animal().. nó so sánh với ví dụ của bạn như thế nào? (ví dụ: là Object.create()gì?)
fusio

@fusio Object.create không gọi hàm tạo ... nếu hàm tạo có tác dụng phụ hoặc tương tự (nó có thể làm bất cứ điều gì mà một hàm bình thường có thể, không giống như trong Java), thì việc gọi nó là không mong muốn khi thiết lập chuỗi kế thừa.
Esailija

3
Tôi vẫn duy trì điều đó đối với những người bắt đầu sử dụng JavaScript, việc hack một giải pháp như thế này không phải là một giải pháp tốt. Có rất nhiều điều kỳ quặc và cạm bẫy không dễ gỡ lỗi mà điều này không nên được khuyên dùng.
m_vdbeek

43

Vì cộng đồng Node.js đảm bảo các tính năng mới từ đặc tả JavaScript ECMA-262 được đưa đến các nhà phát triển Node.js một cách kịp thời.

Bạn có thể xem qua các lớp JavaScript . Liên kết MDN tới các lớp JS Trong ECMAScript 6 lớp JavaScript được giới thiệu, phương pháp này cung cấp cách dễ dàng hơn để mô hình hóa các khái niệm OOP trong Javascript.

Lưu ý : Các lớp JS sẽ chỉ hoạt động ở chế độ nghiêm ngặt .

Dưới đây là một số khung của lớp, kế thừa được viết bằng Node.js (Phiên bản Node.js được sử dụng v5.0.0 )

Khai báo lớp:

'use strict'; 
class Animal{

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

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

var a1 = new Animal('Dog');

Di sản :

'use strict';
class Base{

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

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

var childObj = new Child();

14

Tôi khuyên bạn nên sử dụng trình inheritstrợ giúp đi kèm với utilmô-đun tiêu chuẩn : http://nodejs.org/api/util.html#util_util_inherits_constructor_superconstructor

Có một ví dụ về cách sử dụng nó trên trang được liên kết.


Đây là câu trả lời hữu ích nhất liên quan đến môi trường NodeJS cốt lõi.
Philzen

1
Có vẻ như hiện không được dùng nữa. Từ liên kết câu trả lời: Lưu ý: không khuyến khích sử dụng use.inherits (). Vui lòng sử dụng lớp ES6 và mở rộng từ khóa để nhận hỗ trợ kế thừa cấp độ ngôn ngữ. Cũng lưu ý rằng hai kiểu không tương thích về mặt ngữ nghĩa.
Frosty Z

11

Đây là video hay nhất về JavaScript hướng đối tượng trên internet:

Hướng dẫn cuối cùng về JavaScript hướng đối tượng

Xem từ đầu đến cuối !!

Về cơ bản, Javascript là một ngôn ngữ dựa trên Prototype khá khác biệt so với các lớp trong Java, C ++, C # và những người bạn phổ biến khác. Video giải thích các khái niệm cốt lõi tốt hơn nhiều so với bất kỳ câu trả lời nào ở đây.

Với ES6 (phát hành năm 2015), chúng tôi có từ khóa "class" cho phép chúng tôi sử dụng các "lớp" Javascript giống như chúng tôi làm với Java, C ++, C #, Swift, v.v.

Ảnh chụp màn hình từ video cho thấy cách viết và khởi tạo một lớp / lớp con Javascript: nhập mô tả hình ảnh ở đây


Tôi đánh giá cao bạn đã cung cấp câu trả lời cho ES6. Cảm ơn bạn! Rất tiếc, tôi không có dữ liệu để xem video dài 27 phút. Tôi sẽ tiếp tục tìm kiếm hướng dẫn bằng văn bản.
tim.rohrer

Cảm ơn vì video. Tôi đã giúp tôi giải đáp rất nhiều thắc mắc về javascript.
Kishore Devaraj

4

Trong cộng đồng Javascript, nhiều người cho rằng không nên sử dụng OOP vì mô hình nguyên mẫu không cho phép thực hiện một OOP nghiêm ngặt và mạnh mẽ. Tuy nhiên, tôi không nghĩ rằng OOP là vấn đề của ngôn ngữ mà là vấn đề của kiến ​​trúc.

Nếu bạn muốn sử dụng một OOP mạnh thực sự trong Javascript / Node, bạn có thể xem qua khung mã nguồn mở full-stack Danf . Nó cung cấp tất cả các tính năng cần thiết cho một mã OOP mạnh (lớp, giao diện, kế thừa, phụ thuộc-tiêm, ...). Nó cũng cho phép bạn sử dụng các lớp giống nhau trên cả phía máy chủ (nút) và máy khách (trình duyệt). Hơn nữa, bạn có thể viết mã các mô-đun danf của riêng mình và chia sẻ chúng với bất kỳ ai nhờ Npm.


-1

Nếu bạn đang làm việc riêng và bạn muốn thứ gần gũi nhất với OOP như bạn thường thấy trong Java hoặc C # hoặc C ++, hãy xem thư viện javascript, CrxOop. CrxOop cung cấp cú pháp hơi quen thuộc với các nhà phát triển Java.

Chỉ cần cẩn thận, OOP của Java không giống như OOP trong Javascript. Để có được hành vi tương tự như trong Java, hãy sử dụng các lớp của CrxOop, không phải cấu trúc của CrxOop và đảm bảo tất cả các phương thức của bạn là ảo. Một ví dụ về cú pháp là,

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

Mã là javascript thuần túy, không có chuyển đoạn. Ví dụ được lấy từ một số ví dụ từ tài liệu chính thức.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.