开发时遇到这样的需求,希望通过一个类似factory的机制来创建对象,这个机制接收两个参数,一个是对象所属的类,另一个是参数数组,例如:

function getObject(cls, args){ //.... }

这里cls是一个类(即javascript函数),args是一个参数数组,例如[arg1, arg2, ....],希望这个函数返回的对象等同于下面代码的效果:

new cls(arg1, arg2...);

容易想到,我们应该用apply方法,因为javascript中构建对象的过程就是执行构造函数的过程。而构造函数与普通函数性质完全一样,只是它会被new关键字自动调用而已。apply方法接收2个对象,第一个是函数运行的上下文对象,即this对象。在这里对象尚未构建,因此我们使用一个空对象:

function getObject(cls, args){ var obj = {}; cls.apply({}, args); return obj; }

为验证可行性,我们创建1个类:

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

下面用getObject创建一个employee对象并确认其属性:

var emp = getObject(employee, ['Jack', 26]); alert(emp.name + ': ' + emp.age);

可以看到,正如我们所希望的,对象被正确创建。

但事情到此并没有完。问题出在apply函数的第一个参数上:我们用了一个空对象。实际上起始对象不一定为空。当empoyee的prototype也被定义时,就不为空了,例如,我们通过prototype为employee增加一个方法:

empoyee.prototype.show = function(){ alert(this.name + ': ' + emp.age); }

再创建对象,并调用show方法:

var emp = getObject(employee, ['Jack', 26]); emp.show();

会出错,说show方法不存在,其原因就是我们只用到了emp的构造函数,却没用到其prototype。

实际上,根据javascript规范,new一个对象时,首先是以函数的prototype对象作为原型,在这个基础上去调用创建新对象。因此我们需要修改getObject方法,让apply以prototype为基础。

但同时,不能直接以employee.prototype作为apply的第一个参数,因为那是直接修改了empoyee的原型。我们需要创建一份prototype的拷贝。如果用过dojo.delegate方法,我们应该知道复制对象最高效的做法,用其思路,来重写getObject方法:

function getObject(cls, args){ function _cls(){}; _cls.prototype = cls.prototype; var obj = new _cls(); cls.apply(obj, args); return obj; } //再执行如下代码: var emp = getObject(employee, ['Jack', 26]); emp.show();

可以看到,show方法也被正确构建。这说明得到了正确的getObject方法。完整的例子可参见:

http://jsfiddle.net/V94Z2/

虽然这个需求的实现看似简单,实际上它要求熟知javascript new关键字的工作原理,知其原理,我们就能成功的把对象的构建分成2个部分,一部分是prototype的复制,另一部分是构造函数的执行。用自己的代码去实现这两部分,就能实现我们需要的功能了。