其实原型链这个机制的本质就是形成对象之间的关联关系。
这样的机制更适合用委托理论而不是类的思想去思考。
接下来看一个例子:首先我们要定义一个名为Task的对象,它会包含所有任务都可以使用的具体行为。我们会把特定的任务对象都关联到Task功能对象上,让它们在需要的时候可以进行委托。
下面是推荐的代码形式,非常简单:
Task = {
setId:function(ID){this.id = ID},
outputId:function(){console.log(this.id)}
};
//让XYZ委托Task
XYZ = Object.create(Task);
XYZ.prepareTask = function(ID,label){
this.setId(ID);
this.label = label;
}
XYZ.outputTaskDetails = function(){
this.outputID();
console.log(this.label);
}
在这段代码中,Task和XYZ并不是类(或者函数),它们是对象。XYZ通过Object.create()创建,并委托了Task对象。
相比于面向对象,这种变成风格被称为“对象关联”(OLOO)。
在js中,原型链机制会把对象关联到其他对象,但确实就是没有类似“类”的抽象机制。
我们通过一个示例代码来比较一下两种设计模式(面向对象与对象关联)具体的实现方法:
function Foo(who){
this.me = who;
}
Foo.prototype.identify = function(){
console.log(this.me);
}
function Bar(who){
Foo.call(this,who);
}
Bar.prototype = Object.create(Foo.prototype);
Bar.prototype.speak = function(){
console.log("hello"+this.identify());
}
var b1 = new Bar("b1");
var b2 = new Bar("b2");
b1.speak();
b2.speak();
这是一个典型的面向对象设计模式,子类Bar继承了父类Foo,然后生成了b1与b2两个实例。
这种风格很常见。
Foo = {
init:function(who){
this.me = who;
},
indentify:function(){
console.log(this.me);
}
};
Bar = Object.create(Foo);
Bar.speak = function(){
console.log("hello"+this.identify());
}
var b1 = Object.create(Bar);
b1.init("b1");
var b2 = Object.create(Bar);
b2.init("b2");
b1.speak();
b2.speak();
这段代码通过把b1委托给Bar,把Bar委托给Foo,实现了三个对象之间的关联。
而非常重要的是,这段代码只是把对象关联起来,并不需要那些模仿类的行为。(构造函数、原型、new)
我们已经看到了“类”与“行为委托”在设计模式上的区别,现在看看如何在真实场景中应用这些方法。
首先是Web开发中非常典型的一种前端场景:创建UI控件。
由于我们可能都习惯了面向对象设计模式,因此很快会相对一个包含所有控件通用行为的父类Widget,和继承父类的特殊控件子类Button:
//父类
function Widget(){
……
}
Widget.prototype.render = function(){
……
}
//子类
function Button(){
//调用父类构造函数
Widtget.call(this,……);
……
}
//让Button继承Widget
Button.prototype = Object.create(Widget.prototype);
//重写render
Button.prototype.render = function(){
//调用父类render
Widget.prototype.render.call(this,……);
……
}
Button.prototype.onClick = function(){
……
}
$(document).ready(function(){
var btn1 = new Button(……);
btn1.render();
})
ES6提供了class语法糖:
class Widget{
constructor(){
……
}
render(){
……
}
}
class Button{
constructor(){
super(……);
……
}
render(){
super(……);
……
}
onClick(){
……
}
}
可以看到,使用了ES6的class之后,第一段代码中很多丑陋的语法都不见了。
尽管语法上得到了改进,但实际上这里没有真正的类,class仍然是通过原型链机制实现的。
下面的例子使用对象关联风格委托来更简单地实现WidgetButton:
var Widget = {
init:function(){
……
},
insert:function(){
……
}
}
var Button = Object.create(Widget);
Button.setup = function(){
//委托调用
this.init(……);
……
}
Button.build = function(){
//委托调用
this.insert(……);
……
};
Button.onClick = function(){
……
}
$(document).ready(function(){
var btn1 = Object.create(Button);
btn1.setup(……);
btn1.build(……);
})
使用对象关联风格来编写代码时不需要把Widget和Button当做父类和子类,相反,Widget只是一个对象,包含一组通用的函数,任何类型的控件都可以委托,Button同样只是一个对象,通过委托关联到Widget。
在委托设计模式中除了建议在对象中使用不相同且更具描述性的方法名之外,还要通过对象关联避免丑陋的显式伪多态调用:Widget.call
和Widget.prototype.render.call
,代之以简单的相对委托调用:this.init
和this.insert
。
而在对象关联中不需要使用构造函数创建新对象,说明可以把创建和初始化分离为两个过程,更好地支持关注分离。