只看见了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