[b]前言[/b]
這篇文章須要知道的一個重要觀點,[[prototype]]機制就是指對象的一個內部連接引用另一個對象。
若是在第一個對象上沒有找到須要的屬性或者方法的引用,引擎就會繼續在[[prototype]]關聯的對象上繼續查找,同理,若是後者中也沒有找到須要的引用就會繼續查找他的[[prototype]].以此類推,這一系列對象的連接被稱爲「原型鏈」。javascript
[b]對比[/b]
面向委託的設計,是不一樣於面向類的設計,咱們要試着把思路從類和繼承的設計模式轉換到委託行爲的設計模式。下面將分別貼出2種設計模式的示例代碼,幫助你進行過渡。
下面是典型的(「原型」)面向對象風格css
//父類 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() + "."); } //實例化Bar var b1 = new Bar("b1"); var b2 = new Bar("b2"); b1.speak();//"hello,I am: b1." b2.speak();//"hello,I am: b2."
子類Bar繼承了父類Foo 而後生成了b1和b2兩個實例,b1 委託了Bar.prototype 後者委託了Foo.prototype 這種風格很常見,你應該很熟悉了。
下面來看看如何使用對象關聯風格來編寫徹底相同的代碼。html
1 Foo = { 2 init: function(who) { 3 this.me = who; 4 }, 5 identify: function() { 6 return "I am" + this.me; 7 } 8 }; 9 Bar = Object.create(Foo); 10 Bar.speak = function() { 11 console.log("Hello, I am: " + this.identify() + "."); 12 } 13 var b1 = Object.create(Bar); 14 b1.init("b1"); 15 var b2 = Object.create(Bar); 16 b2.init("b2"); 17 18 b1.speak(); 19 b2.speak();
這段代碼中咱們一樣利用了[[prototype]]吧b1委託給了Bar並把Bar委託給了Foo,和上一段代碼如出一轍的。仍然實現了三個對象之間的關聯,
但很是重要的一單是,這段代碼簡潔了許多,咱們只是把對象關聯起來,並不須要那些即複雜又使人困惑的模仿類的行爲(構造函數,原型以及new)。
問問你本身,若是對象關聯的代碼可以實現類風格代碼的全部功能而且更加簡潔易懂,那她是否是比類風格更好?前端
下面來看看兩段代碼所對應的思惟模型。
首先,類風格代碼的思惟模型強調以實體以及實體間的關係:
java
這張圖有點不清晰甚至誤導人,並且這是十分複雜的關係網。從關係圖來看,還有類繼承的一個坑,Bar.prototype繼承Foo.prototype以後丟失了constructor。一般須要手動將Bar的constructor指回Bar自身。
如今讓咱們看看對象關聯代碼的思惟模型
jquery
經過比較能夠看出,對象關聯風格的代碼顯然更加簡潔,由於這種代碼只關注一件事情:對象之間的關聯聯繫。
經典的「類」技巧很是複雜並且使人困惑,去掉他們以後,事情變得簡單許多。web
[b]前端開發中真實場景的對比[/b]
咱們已經看到「類」和「行爲委託」在理論和思惟模型方面的區別,如今看看真實場景如何應用這些方法
來看看web開發中很是經典的一種前端場景,建立UI控件。好比「Button」控件
若是你已經習慣了面向對象的設計模式,會用JavaScript實現類風格的代碼設計模式
1 <!doctype html> 2 <html lang="en"> 3 <head> 4 <meta charset="UTF-8" /> 5 <title>Document</title> 6 <style type="text/css"> 7 8 </style> 9 </head> 10 <body> 11 <script src="https://cdn.bootcss.com/jquery/2.1.1/jquery.js"></script> 12 <script type="text/javascript"> 13 //父類 14 function Widget(width, height) { 15 this.width = width || 50; 16 this.height = height || 50; 17 this.$elem = null; 18 } 19 20 Widget.prototype.render = function($where) { 21 if(this.$elem) { 22 this.$elem.css({ 23 width: this.width + "px", 24 height: this.height + "px" 25 }).appendTo($where); 26 } 27 } 28 29 //子類 30 function Button(width, height, label) { 31 //調用super 構造函數 32 Widget.call(this, width, height); 33 this.label = label || "Default"; 34 35 this.$elem = $("<button>").text(this.label); 36 } 37 38 //讓Button 繼承 Widget 39 Button.prototype = Object.create( Widget.prototype); 40 41 //重寫 render() 42 Button.prototype.render = function($where) { 43 //"super" 調用 44 Widget.prototype.render.call(this, $where); 45 this.$elem.click( this.onClick.bind(this)); 46 } 47 48 Button.prototype.onClick = function(evt) { 49 console.log("Button: " + this.label + "被點擊了"); 50 alert("Button: " + this.label + "被點擊了"); 51 } 52 53 $(function() { 54 var $body = $(document.body); 55 var btn1 = new Button(123, 30, "hello"); 56 var btn2 = new Button(150, 40, "World"); 57 58 btn1.render($body); 59 btn2.render($body); 60 }) 61 </script> 62 </body> 63 </html>
在面向對象設計模式中,咱們首先須要在父類中定義基礎的render(..),而後在子類中重寫他,子類並不會替換基礎的render(..)。只是添加一些按鈕特有的行爲。好比點擊按鈕彈出對話框
恩,這裏還出現了醜陋的顯式僞多態,即經過widget.call和widget.prototype.render.call從子類方法中引用"父類」的基礎方法。太醜陋這算是缺點吧。架構
下面的例子使用對象關聯風格委託來更簡單地實現app
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Document</title> <style type="text/css"> </style> </head> <body> <script src="https://cdn.bootcss.com/jquery/2.1.1/jquery.js"></script> <script type="text/javascript"> var Widget = { init: function(width, height) { this.width = width || 50; this.height = height || 50; this.$elem = null; }, insert: function($where) { if(this.$elem) { this.$elem.css({ width: this.width + 'px', height: this.height + "px" }).appendTo($where); } } }; var Button = Object.create(Widget); Button.setup = function(width, height, label) { this.init(width, height); this.label = label || "default"; this.$elem = $("<button>").text(this.label) } Button.build = function($where) { //調用委託 this.insert($where) this.$elem.click(this.onClick.bind(this)); } Button.onClick = function(evt) { alert("按鈕: " + this.label + "被點擊了"); } $(function() { var $body = $(document.body); var btn1 = Object.create(Button); btn1.setup(125, 30, "hello"); var btn2 = Object.create(Button); btn2.setup(150, 40, "World"); btn1.build($body); btn2.build($body); }) </script> </body> </html>
使用對象關聯風格來編寫代碼不須要吧Widget和Button當作父類和子類。相反,Widget只是一個對象,包含一組通用的函數,任何類型的控件均可以委託。
從語法的角度來講,咱們一樣沒有任何構造函數,.prototype或new,實際上沒有使用它們的必要。
[b]小結[/b]
在軟件架構中你能夠選擇是否使用類和繼承設計模式,大多數開發者理所固然地認爲類是惟一的代碼組織方式,但這裏咱們看到了另一種更少見但更強大的設計模式:行爲委託。
行爲委託認爲對象之間是兄弟關係,相互委託,而不是父類和子類的關係,JavaScriptd [[prototype]]機制本質上就是行爲委託機制,也就是說,咱們能夠選擇在JavaScript中努力實現「類」機制。也能夠擁抱更加天然的[[prototype]]委託機制。
當你只用對象來設計代碼時,不只可讓語法更加簡潔,也可讓代碼結構更加清晰。
對象關聯是一種編碼風格。他倡導的是直接建立和關聯對象,不拔他們抽象成類。對象關聯能夠用於基於[[prototype]]的行爲委託很是天然的實現。
參考自 《你不知道的JavaScript》上卷 第六章