究竟怎样OOP?
发布于 4年前 作者 pengchun 2830 次浏览 最后一次编辑是 3年前

只看见了OO就浮想联翩的同学请面壁去。说实在的,为这个题目我纠结了很久,JavaScript中本身一切就是对象(Object),那为什么还要纠结怎样OOP呢?

接yixuan的话题,我先说一个例子。在用NodeJS写程序的过程中,我们常常碰到这样的代码(person.js):


/* vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: /

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

return p;
}


我们在另一个文件test_class.js中这样使用:


var Person = require(‘./person.js’);

var user = Person.create('aleafs’, 28);
user.echo();


运行test_class.js,不出我们所料,结果如下:

$ node test_class.js
Name: aleafs, Age: 28


如果只求实现功能,我想我们的目的达到了,并且在大多数情况下它能运行得很好。但是,如果创建的person对象很多,我们知道,每一个person对象都要在内存中占用一块地方存放它的echo方法——尽管echo方法对每一个实例都是完全相同的,显然有点浪费对不对?

与上面的代码类似的,person.js还有如下的实现方式:


/
vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: /

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

这样的实现多少想点OOP的程序了,对吧?可是它仍然存在echo方法的内存浪费问题。为此,我们寄希望于prototype机制:

/
vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: /

var Person = function(name, age) {
this.name = name;
this.age = age;
}

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

exports.create = Person;


由于在JavaScript中,对同一个类的多个对象而言,prototype只被解析并保存一份实例,这个方法确实能够避免我们上面提到的内存浪费问题。但是另一个问题来了,我们对test_class.js原封不动,运行之:


$node test_class.js
node.js:116
throw e; // process.nextTick error, or ‘error’ event on first tick
^
TypeError: Cannot call method ‘echo’ of undefined
at Object. (/home/pengchun/b.js:4:6)
at Module._compile (module.js:373:26)
at Object…js (module.js:379:10)
at Module.load (module.js:305:31)
at Function._load (module.js:271:10)
at Array. (module.js:392:10)
at EventEmitter._tickCallback (node.js:108:26)

什么情况!居然说找不到echo方法!哦,你一定想到了,在test_class.js中我们不能继续使用


var user = Person.create('aleafs’, 28);

了,我们得用new创建一个对象,像这样:


var user = new Person.create('aleafs’, 28);


试了一下,果然能运行了:
$ node test_class.js
Name: aleafs, Age: 28

爷爷的,这不是坑爹吗?有没有new并不报语法错误,却在运行时报错。实际上,在上面第二种方法实现person类的情况下也存在这个问题,我们不说了。看看我是怎么解决这个问题的:

/
vim: set expandtab tabstop=4 shiftwidth=4 foldmethod=marker: */

var Person = function(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);
};


看见了没,我仍然采用prototype的方法来定义类。不同的是,我用exports对象指定输出变量时,没有直接把Person类赋值给create,而是创建了一个函数,在这个函数内new了一个对象返回。这样就避免了在外部使用new关键字的问题。

这个方法很好,某某大师也推荐过。我继续推荐这篇文章:
http://www.ruanyifeng.com/blog/2010/05/object-oriented_javascript_encapsulation.html

13 回复

最后的解决方案有个模式术语叫工厂方法。

其实在javascript或者python这些脚本语言里面,很多对象都是静态单件对象,我常常喜欢最简单的方式,因为它在整个进程里无需实例化的,正如nodejs的module.exports。

var foo = {
echo: function() {
console.log(‘hello world’);
}
};

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

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

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

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

Person是拼写错了,多谢纠正。

外部使用的时候出于习惯我常常采用静态调用的方式,例如:

var user = Person.create('name’, age);

在person.js中包一层也就避免了使用上的不一致,你new或者不new,它返回的都是一样的东西

YUI3里是这样的 我认为也不错
var Person = function(name, age) {
if (! this instanceof Person){
return new Person(name, age);
}
this.name = name;
this.age = age;
}

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

exports.create = Person;

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

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

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

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

建议用Person.new('somebody’, 22)
更优雅

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

回到顶部