Nodejs中究竟怎样OOP?

Nodejs中究竟怎样OOP?

只看见了OO就浮想联翩的同学请面壁去。说实在的,为这个题目我纠结了很久,JavaScript中本身一切就是对象(Object),那为什么还要纠结怎样OOP呢? <br/> <br/>接yixuan的话题,我先说一个例子。在用NodeJS写程序的过程中,我们常常碰到这样的代码(person.js): <br/> <br/><pre lang=“javascript”> <br/>/* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: / <br/> <br/>exports.create = function(name, age) { <br/> var p = { <br/> name : name, <br/> age : age, <br/> echo : function() { <br/> console.log("Name: " + name + ", Age: " + age); <br/> } <br/> } <br/> <br/> return p; <br/>} <br/></pre> <br/> <br/>我们在另一个文件test_class.js中这样使用: <br/> <br/><pre lang=“javascript”> <br/>var Person = require(’./person.js’); <br/> <br/>var user = Person.create(‘aleafs’, 28); <br/>user.echo(); <br/></pre> <br/> <br/>运行test_class.js,不出我们所料,结果如下: <br/> <br/><blockquote>$ node test_class.js <br/>Name: aleafs, Age: 28 <br/></blockquote> <br/> <br/>如果只求实现功能,我想我们的目的达到了,并且在大多数情况下它能运行得很好。但是,如果创建的person对象很多,我们知道,每一个person对象都要在内存中占用一块地方存放它的echo方法——尽管echo方法对每一个实例都是完全相同的,显然有点浪费对不对? <br/> <br/>与上面的代码类似的,person.js还有如下的实现方式: <br/> <br/><pre lang=“javascript”> <br/>/ vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: / <br/> <br/>exports.create = function(name, age) { <br/> this.name = name; <br/> this.age = age; <br/> this.echo = function() { <br/> console.log("Name: " + this.name + ", Age: " + this.age); <br/> } <br/>} <br/></pre> <br/>这样的实现多少想点OOP的程序了,对吧?可是它仍然存在echo方法的内存浪费问题。为此,我们寄希望于prototype机制: <br/><pre lang=“javascript”> <br/>/ vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: / <br/> <br/>var Person = function(name, age) { <br/> this.name = name; <br/> this.age = age; <br/>} <br/> <br/>Person.prototype.echo = function() { <br/> console.log("Name: " + this.name + ", Age: " + this.age); <br/>} <br/> <br/>exports.create = Person; <br/> <br/></pre> <br/>由于在JavaScript中,对同一个类的多个对象而言,prototype只被解析并保存一份实例,这个方法确实能够避免我们上面提到的内存浪费问题。但是另一个问题来了,我们对test_class.js原封不动,运行之: <br/> <br/> <br/><blockquote>$node test_class.js <br/>node.js:116 <br/> throw e; // process.nextTick error, or ‘error’ event on first tick <br/> ^ <br/>TypeError: Cannot call method ‘echo’ of undefined <br/> at Object. (/home/pengchun/b.js:4:6) <br/> at Module._compile (module.js:373:26) <br/> at Object…js (module.js:379:10) <br/> at Module.load (module.js:305:31) <br/> at Function._load (module.js:271:10) <br/> at Array. (module.js:392:10) <br/> at EventEmitter._tickCallback (node.js:108:26) <br/></blockquote> <br/>什么情况!居然说找不到echo方法!哦,你一定想到了,在test_class.js中我们不能继续使用 <br/> <br/><pre lang=“javascript”> <br/>var user = Person.create(‘aleafs’, 28); <br/></pre> <br/>了,我们得用new创建一个对象,像这样: <br/> <br/><pre lang=“javascript”> <br/>var user = new Person.create(‘aleafs’, 28); <br/></pre> <br/> <br/>试了一下,果然能运行了: <br/><blockquote>$ node test_class.js <br/>Name: aleafs, Age: 28 <br/></blockquote> <br/>爷爷的,这不是坑爹吗?有没有new并不报语法错误,却在运行时报错。实际上,在上面第二种方法实现person类的情况下也存在这个问题,我们不说了。看看我是怎么解决这个问题的: <br/><pre lang=“javascript”> <br/>/ vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */ <br/> <br/>var Person = function(name, age) { <br/> this.name = name; <br/> this.age = age; <br/>} <br/> <br/>Person.prototype.echo = function() { <br/> console.log(“Name: " + this.name + “, Age: " + this.age); <br/>} <br/> <br/>exports.create = function(name, age) { <br/> return new Person(name, age); <br/>}; <br/></pre> <br/> <br/>看见了没,我仍然采用prototype的方法来定义类。不同的是,我用exports对象指定输出变量时,没有直接把Person类赋值给create,而是创建了一个函数,在这个函数内new了一个对象返回。这样就避免了在外部使用new关键字的问题。 <br/> <br/>这个方法很好,某某大师也推荐过。我继续推荐这篇文章: <br/><a target=”_blank” href=“http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_encapsulation.html”>http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_encapsulation.html</a>


15 回复

在Node.js中实现面向对象编程(OOP)是一个常见的需求,尤其是在处理复杂的应用逻辑时。虽然JavaScript本质上是一种基于原型的编程语言,但我们可以借鉴传统的面向对象概念来更好地组织代码。

示例代码

首先,我们来看一个简单的Person类的实现:

person.js

// 定义Person构造函数
function Person(name, age) {
    this.name = name;
    this.age = age;
}

// 在原型上定义方法
Person.prototype.echo = function() {
    console.log(`Name: ${this.name}, Age: ${this.age}`);
};

// 输出创建Person对象的方法
module.exports.create = function(name, age) {
    return new Person(name, age);
};

在这个例子中,我们定义了一个Person构造函数,并在原型上添加了一个echo方法。通过这种方式,每个Person对象共享同一个echo方法,从而节省了内存。

test_class.js

const Person = require('./person.js');

const user = Person.create('aleafs', 28);
user.echo();  // 输出: Name: aleafs, Age: 28

解释

  1. 构造函数Person构造函数用于初始化新的Person对象。使用this关键字来引用新创建的对象。

  2. 原型方法:在Person.prototype上定义的方法会被所有Person实例共享。这样可以确保每个实例不会重复存储相同的方法。

  3. 模块导出:通过module.exports.create导出一个创建Person对象的方法。这样在其他文件中可以通过require导入并使用该方法。

优点

  • 内存优化:由于方法是在原型上定义的,所有实例共享这些方法,因此节省了内存。
  • 代码组织:通过构造函数和原型方法的方式,代码结构更加清晰,便于维护和扩展。

总结

虽然JavaScript不是一种传统意义上的面向对象语言,但我们可以通过构造函数、原型链等机制来实现面向对象的编程风格。这不仅有助于代码的组织和复用,还能提高程序的性能。上述代码展示了如何在Node.js项目中应用这种模式,以实现更高效和可维护的代码。


最后的解决方案有个模式术语叫工厂方法。 <br/> <br/>其实在javascript或者python这些脚本语言里面,很多对象都是静态单件对象,我常常喜欢最简单的方式,因为它在整个进程里无需实例化的,正如nodejs的module.exports。 <br/> <br/>var foo = { <br/> echo: function() { <br/> console.log(‘hello world’); <br/> } <br/>};

var user = Persion.create(‘aleafs’, 28); 这个就是调用 Person(name, age)啊,你这个函数根本就没返回值,所以user是undefined啊。 <br/>new Person(name, age) 返回的是函数本身啊(因为Person函数没有返回对象)。 <br/>PS:在外部使用new关键字为什么会是个问题? <br/>再PS: Persion 是不是拼错了?

你这个只适用于静态方法。对于需要实例属性和继承的,就没这么简单了。

没错,实例化也是有开销的。所以这个东西还得看你怎么用

是的,prototype一个最大的好处就是方便继承

Person是拼写错了,多谢纠正。 <br/> <br/>外部使用的时候出于习惯我常常采用静态调用的方式,例如: <br/> <br/>var user = Person.create(‘name’, age); <br/> <br/>在person.js中包一层也就避免了使用上的不一致,你new或者不new,它返回的都是一样的东西

YUI3里是这样的 我认为也不错 <br/>var Person = function(name, age) { <br/>if (! this instanceof Person){ <br/>return new Person(name, age); <br/>} <br/> this.name = name; <br/> this.age = age; <br/>} <br/> <br/>Person.prototype.echo = function() { <br/> console.log("Name: " + this.name + ", Age: " + this.age); <br/>} <br/> <br/>exports.create = Person;

原则还是把new包装在“类”库的内部,外部调用只有函数,保持这个原则,在JS中很重要。上升到OOP的高度,总会让很多人很纠结。

同意,原则上js本身就是一个脚本语言,为啥非要带个new整的跟java似的。根据不同的使用需求使用最高效的代码即可。这种方式个人很推荐。因为刚开发了个web im要弹出很多对话框,看着一堆new也挺闹心。这种包装符合个人喜好。关键是对于内存开销的节省。非常OK

记得很早之前,有个朋友也说过关于new的使用,,于是翻译了D.C文章,文章在: <br/>《[翻译]废掉new》 <br/>http://forum.ajaxjs.com:8080/viewthread.php?tid=54&extra=page%3D1 <br/>希望对怎么理解new有帮助

发现用coffeescript写node程序太爽了,对原生的OO进行了一定的扩展,好用多了。另外backbone也是个好东西,很喜欢里面的bind,可以解决回调函数的this问题。哦,再加一个:underscore

建议用Person.new(‘somebody’, 22) <br/>更优雅

Person.new(‘somebody’, 22) 怎么写?

在Node.js中实现面向对象编程(OOP)通常涉及使用构造函数、原型(prototype)和模块化等概念。以下是通过一些具体的示例来说明如何在Node.js中进行OOP。

示例1:基本的对象创建

首先,我们可以创建一个简单的对象来表示一个Person

// person.js
exports.create = function(name, age) {
    var p = {
        name: name,
        age: age,
        echo: function() {
            console.log("Name: " + this.name + ", Age: " + this.age);
        }
    };
    return p;
};

然后在另一个文件中使用这个对象:

// test_class.js
var Person = require('./person.js');
var user = Person.create('aleafs', 28);
user.echo(); // 输出: Name: aleafs, Age: 28

这种方法简单,但每个Person对象都会拥有一个独立的echo方法,这会导致内存浪费。

示例2:使用构造函数和原型

为了优化内存使用,可以使用构造函数和原型来定义Person对象:

// person.js
function Person(name, age) {
    this.name = name;
    this.age = age;
}

Person.prototype.echo = function() {
    console.log("Name: " + this.name + ", Age: " + this.age);
};

exports.create = function(name, age) {
    return new Person(name, age);
};

在另一个文件中使用这个对象:

// test_class.js
var Person = require('./person.js');
var user = Person.create('aleafs', 28);
user.echo(); // 输出: Name: aleafs, Age: 28

这里,Person构造函数定义了属性,而echo方法则添加到了Person.prototype上,确保所有实例共享同一份方法。

总结

通过上述示例,可以看到在Node.js中实现OOP的关键在于合理利用构造函数和原型,以避免不必要的内存开销。构造函数用于初始化对象的属性,而原型用于定义可共享的方法。这种方式不仅提高了代码的复用性,还优化了内存使用。

回到顶部