究竟怎样OOP?
发布于 13 年前 作者 pengchun 8993 次浏览 最后一次编辑是 8 年前

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

13 回复

最后的解决方案有个模式术语叫工厂方法。 <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) 怎么写?

回到顶部