类是一种可选(而不是必须)的设计模式,而且在 JavaScript 使用这样的 [[Prototype]] 语言中实现类是很别扭的,它会存在很多的缺点,列如:繁琐杂乱的 .prototype引用、试图调用原型链上层同名函数时的显式伪多态以及不可靠、不美观而且容易被误解成“构造函数”的 .constructor 。
除此之外,类设计还存在更加深刻的问题。传统面向类的语言中父类和子 类、子类和实例之间其实是复制操作,但是在 [[Prototype]] 中并没有复制,相反,它们之间只有委托关联。
先看一段代码:
var Task = {
setId: function (ID) {
this.id = ID
},
outputID: function () {
console.log(this.id)
}
};
var XYZ = Object.create(Task);
XYZ.prepareTak = function (ID, Label) {
this.setID(ID);
this.label = Label
}
XYZ.outputTaskDetails = function () {
this.outputID();
console.log(this.label)
}
var ABC = Object.create(Task)
......
复制代码
在这段代码中,Task和XYZ并不是类,他们只是对象。A通过Object.create(...)创建,它的[[Prototype]]委托了Task对象。
委托行为意味着某些对象(XYZ)在找不到属性或者方法引用时会把这个请求委托给另一个对象。
比较
首先是典型的(“原型”)面向对象风格
function Foo(who) {
this.me = who;
}
Foo.prototype.identify = function() {
return "I am " + this.me;
};
function Bar(who) {
Foo.call( this, who );
}
Bar.prototype = Object.create( Foo.prototype );
Bar.prototype.speak = function() {
alert( "Hello, " + this.identify() + "." );
};
var b1 = new Bar( "b1" );
var b2 = new Bar( "b2" );
b1.speak();
b2.speak();
复制代码
子类 Bar 继承了父类 Foo ,然后生成了 b1 和 b2 两个实例。 b1 委托了 Bar.prototype ,后者 委托了 Foo.prototype 。
Foo = {
init: function(who) {
this.me = who;
},
identify: function() {
return "I am " + this.me;
}
};
Bar = Object.create( Foo );
Bar.speak = function() {
alert( "Hello, " + this.identify() + "." );
};
var b1 = Object.create( Bar );
b1.init( "b1" );
var b2 = Object.create( Bar );
b2.init( "b2" );
b1.speak();
b2.speak();
复制代码
我们用对象关联的方式同样实现了上述的逻辑,但重要的一点是这段代码简洁了许多,我们只是把对象关联了起来,并不需要那些令人困惑又难受的模仿类的行为(如构造函数、new);
JavaScript 中的函数之所以可以访问 call(..) 、 apply(..) 和 bind(..),就是因为函数本身是对象。而函数对象同样有 [[Prototype]] 属性并且关联到 Function.prototype 对象,因此所有函数对象都可以通过委托调用这些默认方法。
对象关联是一种编码风格,它倡导的是直接创建和关联对象,不把它们抽象成类。对象关联可以用基于 [[Prototype]] 的行为委托非常自然地实现。