程式碼重複使用及其原則
<font face="NSimsun">代码复用</font>
,顧名思義就是對曾經編寫過的程式碼的一部分甚至全部重新加以利用,從而建立新的程式。在談到程式碼復用的時候,我們首先可以想到的是<font face="NSimsun">继承性</font>
。程式碼復用的原則是:
<code>优先使用对象组合,而不是类继承</code>
在js中,由於沒有類別的概念,因此實例的概念也就沒多大意義,js中的物件是簡單的鍵-值對,可以動態的建立和修改它們。
但在<font face="NSimsun">js</font>
中,我們可以使用建構子和<font face="NSimsun">new</font>
操作符來實例化一個對象,這與其他使用類別的程式語言在語法上有其相似之處。
例如:
<code>var trigkit4 = new Person();</code>
<font face="NSimsun">js</font>
在調用構造函數<font face="NSimsun">Person</font>
時似乎看起來是一個類,但其實際上仍然是一個函數,這讓我們產生了一些假定在類的基礎上的開發思路和繼承模式,我們可以稱之為「類別式繼承模式」。
傳統的繼承模式是需要<font face="NSimsun">class</font>
關鍵字的,我們假定以上的類別繼承模式為<font face="NSimsun">现代继承模式</font>
,這是一種不需要以類別的方式考慮的模式。
類別繼承模式
看下面兩個建構子<font face="NSimsun">Parent()</font>
和<font face="NSimsun">Child()</font>
的範例:
<code><script type="text/javascript"><br> function Parent(name){<br> this.name = name || 'Allen';<br> }<br> Parent.prototype.say = function(){<br> return this.name;<br> }<br> function Child(name){}<br> //用Parent构造函数创建一个对象,并将该对象赋值给Child原型以实现继承<br> function inherit(C,P){<br> C.prototype = new P();//原型属性应该指向一个对象,而不是函数<br> }<br> //调用声明的继承函数<br> inherit(Child,Parent);<br></script></code>
當使用<font face="NSimsun">new Child()</font>
語句建立一個物件時,它會透過原型從<font face="NSimsun">Parent()</font>
實例取得它的功能,例如:
<code>var kid = new Child(); kid.say(); //Allen</code>
原型鏈
討論一下類別繼承模式下原型鏈的工作原理,我們將物件看做是記憶體中某處的區塊,該記憶體區塊包含資料以及指向其他區塊的參考。當用<font face="NSimsun">new Parent()</font>
語句建立一個物件時,就會建立如下圖左邊的這樣一個區塊,這個區塊保存了<font face="NSimsun">name</font>
屬性,如果想存取<font face="NSimsun">say()</font>
方法,我們可以透過指向建構子<font face="NSimsun">Parent()</font>
的<font face="NSimsun">prototype</font>
(原型)屬性的隱式連結<font face="NSimsun">__proto__</font>
,便可存取右邊區塊<font face="NSimsun">Parent.prototype</font>
。
那麼,當使用<font face="NSimsun">var kid = new Child()</font>
建立新物件時會發生什麼?如下圖:
使用<font face="NSimsun">new Child()</font>
語句所建立的物件除了隱式連結<font face="NSimsun">__proto__</font>
外,它幾乎是空的。在這種情況下,<font face="NSimsun">__proto__</font>
指向了在<font face="NSimsun">inherit()</font>
函數中使用<font face="NSimsun">new Parent()</font>
語句所建立的物件
當執行<font face="NSimsun">kid.say()</font>
時,由於最左下角的區塊對象並沒有<font face="NSimsun">say()</font>
方法,因此他將透過原型鏈查詢中間的區塊對象,然而,中間的區塊對像也沒有<font face="NSimsun">say()</font>
方法,因此他又順著原型鏈查詢到最右邊的區塊對象,而該對象正好有<font face="NSimsun">say()</font>
方法。完了嗎?
執行到這裡的時候並沒有完,在<font face="NSimsun">say()</font>
方法中引用了<font face="NSimsun">this.name</font>
,this指向構造函數所創建的對象,在這裡,它指向了<font face="NSimsun">new Child()</font>
這個區塊,然而,<font face="NSimsun">new Child()</font>
中並沒有<font face="NSimsun">name</font>
屬性,為此,將查詢中間區塊,而中間區塊正好有<font face="NSimsun">name</font>
屬性,至此,原型鏈的查詢完畢。
更詳細的討論請看我這篇文章:javascript學習筆記(五)原型與原型鏈詳解
共享原型
本模式的法則在於:可重複使用的成員應該轉移到原型而不是放置在this中。因此,處於繼承的目的,任何值得繼承的東西都應該放在原型中實現。所以,可以將子物件的原型與父物件的原型設定為相同即可,如下範例所示:
<code>function inherit(C,P){<br> C.prototype = P.prototype;<br>}</code>
子对象和父对象共享同一个原型,并且可以同等的访问<font face="NSimsun">say()</font>
方法。然而,子对象并没有继承<font face="NSimsun">name</font>
属性
原型继承
原型继承是一种“现代”无类继承模式。看如下实例:
<code><script type="text/javascript"><br> //要继承的对象<br> var parent = {<br> name : "Jack" //这里不能有分号哦<br> };</code> <code> //新对象<br> var child = Object(parent);</code> <code> alert(child.name);//Jack<br></script></code>
在原型模式中,并不需要使用对象字面量来创建父对象。如下代码所示,可以使用构造函数来创建父对象,这样做的话,自身的属性和构造函数的原型的属性都将被继承。
<code><script type="text/javascript"><br> //父构造函数<br> function Person(){<br> this.name = "trigkit4";<br> }<br> //添加到原型的属性<br> Person.prototype.getName = function(){<br> return this.name;<br> };<br> //创建一个新的Person类对象<br> var obj = new Person();<br> //继承<br> var kid = Object(obj);<br> alert(kid.getName());//trigkit4<br></script></code>
本模式中,可以选择仅继承现有构造函数的原型对象。对象继承自对象,而不论父对象是如何创建的,如下实例:
<code><script type="text/javascript"><br> //父构造函数<br> function Person(){<br> this.name = "trigkit4";<br> }<br> //添加到原型的属性<br> Person.prototype.getName = function(){<br> return this.name;<br> };<br> //创建一个新的Person类对象<br> var obj = new Person();<br> //继承<br> var kid = Object(Person.prototype);<br> console.log(typeof kid.getName);//function,因为它在原型中<br> console.log(typeof kid.name);//undefined,因为只有该原型是继承的<br></script></code>