其實原型鏈這個機制的本質就是形成對象之間的關聯關系。
這樣的機制更適合用委托理論而不是類的思想去思考。
接下來看一個例子:首先我們要定義一個名為Task的對象,它會包含所有任務都可以使用的具體行為。我們會把特定的任務對象都關聯到Task功能對象上,讓它們在需要的時候可以進行委托。
下面是推薦的代碼形式,非常簡單:
Task = { setId:function(ID){this.id = ID}, outputId:function(){console.log(this.id)}};//讓XYZ委托TaskXYZ = 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繼承WidgetButton.prototype = Object.create(Widget.prototype);//重寫renderButton.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
。
而在對象關聯中不需要使用構造函數創建新對象,說明可以把創建和初始化分離為兩個過程,更好地支持關注分離。
新聞熱點
疑難解答