OOP的特性之一就是繼承,只有實現了繼承的語言才能稱之爲OOP,本篇將說明javasctipt如何使用繼承。
原型、構造函數和對象中的基於原型的繼承概念javascript
雖然本地對象都繼承自Object,但實際上,能夠認爲全部對象都是繼承自原型。在JAVA中,定義了一個根對象Object,全部對象都從根對象繼承而來,從而創建基於類的對象體系。那麼咱們也能夠認爲javascript中,定義了原型對象,全部類基於原型而創建了對象體系。java
這個繼承過程是這樣的:編程
一、當定義函數(構造函數也是函數)的時候,就自動爲其設置了一個原型屬性,原型屬性的值是原型對象,原型對象只有一個函數constructor,並指向關聯的構造函數。瀏覽器
二、以構造函數的方式定義類,類的原型的值就是這個構造函數原型的引用。函數
三、從類實例化對象,執行操做符new的時候,建立了一個沒有屬性空對象,賦值給this關鍵字。自動初始化對象的原型,值爲構造函數的原型對象引用。工具
這就是原型的繼承過程,在定義類的時候,給原型定義的屬性和方法,就可以被有效繼承,從而完整是實現了OOP的對象繼承機制。學習
問題:在構造函數而不是構造函數的原型中定義屬性和方法的方式,是否也會被類的實例對象繼承呢?這個問題放到後面可以理解的時候來解答。this
基於原型繼承的屬性讀取是寫入是不對稱的,過程式這樣的:spa
一、屬性讀取:在實例對象o中讀取屬性p:o.p,先從o中查找屬性p,若是沒有找到,從o.prototype中查找,這樣基於原型的繼承可以奏效;prototype
二、屬性寫入:設置實例對象o屬性p的值:o.p=1,則不會使用原型對象,而直接給實例對象屬性p寫入值,若是沒有找到屬性p,新增屬性p並賦值,而不會深刻到原型中。這樣的目的是避免一個實例對象對原型的影響被擴散到其餘實例對象。
Object.hasOwnProperty(/*propertyName*/)方法能夠區分基於原型繼承屬性和常規屬性。
類和實例屬性和方法
類和實例的屬性和方法分別稱爲:實例屬性、實例方法、類屬性、類方法,顧名思義,屬性和方法是定義在類仍是類實例中,並分別在類和類實例中調用,經過一個例子進行說明會更清晰易懂。javascript中能夠模擬實現JAVA中的類和實例的方法、屬性。
1 //類Circle 2 3 function Circle(radius){ 4 this.r=radius; //實例屬性,在構造函數中定義並初始化 5 } 6 7 Circle.PI = 3.14159; //類屬性,實際是構造函數的屬性 8 9 Circle.prototype.area = function(){return Circle.PI*this.r*this.r;} //計算圓的面積的實例方法 10 11 Circle.max = function(a,b){ 12 if (a.r>b.r) return a; 13 else return b; 14 } //類方法定義,返回面積較大的圓對象 15 16 var c = new Circle(1.0); //建立類實例 17 c.r = 2.2; //設置r實例屬性 18 19 var a=c.area(); //調用area()實例方法 20 var x=Math.exp(Circle.PI); //使用類屬性 21 22 var d=new Circle(11.2) //建立另一個實例 23 var bigger = Circle.max(c,d); //調用類方法
本質上,類屬性和類方法是全局可訪問的。在JAVA中,類若是沒有被實例化,只有靜態屬性和方法可以使用,javascript中由於是全局的,能夠模擬這個狀況。
一、在構造函數外不添加到原型對象的屬性和方法,模擬爲類方法和屬性,經過類直接調用。相似JAVA中的靜態方法;
二、在構造函數和構造函數外添加到原型的屬性和方法,模擬爲實例屬性和方法,須要實例化對象中調用。相似JAVA中的普通方法。
雖然在語法上沒有嚴格限制這麼作, 但這麼作的好處是顯而易見的。javacript對象編程具備太多的可能性,咱們須要創建本身的編程風格。若是但願在類實例中使用的屬性和方法,在構造函數和原型中定義,不然在構造函數外,不使用原型來定義。
類層次的繼承
在JAVA等基於類繼承的語言中,提供了一個根類Object,全部類都是根類的子類,類之間能夠相互繼承,從而創建類層次結構,javascript也採用相似類層次。Object類是通用類,是全部本地對象的超類,全部對象都繼承了它部分屬性。
問題:前面可能說到javascript沒有基於根類來建立類層次的方式,是源於前面的理解,且找了不少資料也沒有全部類都繼承自Object的說話。犀牛書「超類和子類」小節明確說是基於根類Object,這個說法的根本在於類如何從Object繼承。咱們先耐着性子看下去。
咱們已經知道了對象如何從原型繼承屬性,但又如何從Object繼承屬性呢。原型自己是一個對象,是由Object構造函數建立的,所以原型對象繼承了Object.prototype屬性。基於原型的繼承不限於一個單一的原型對象,它包含了一個原型鏈,繼承了原型鏈中全部原型的屬性。好比Circle對象繼承了Circle.prototype和Object.prototype的同名屬性。
問題:當從函數建立類,如何從Object繼承?這個過程是否是默認自動進行的?
建立javascript類的子類
遺憾的是,這不是一個清晰簡單的過程,所以而備受責備。下面用一個例子來講明。
//建立一個矩形類,使用一個構造函數 function Rectangle(w,h){ this.width=w; this.height=h; } Rectangle.prototype.area=function(){ return this.width*this.height; } //至此矩形類建立完畢,下面爲其建立一個子類,子類擴展父類,增長一個位置方法。 function PositionedRectangle(x,y,w,h){ //首先在新對象調用父類的構造函數,初始化寬度和高度。 //使用call方法調用構造函數 Rectangle.call(this,w,h); //如今保存矩形的位置 this.x=x; this.y=y; } /** *若是使用定義PositionedRectangle()構造函數是默認的原型對象, *將得到一個Object對象子類。爲了得到Rectangle對象子類,必須顯 *式建立咱們本身的原型對象,原型屬性的值是Rectangle對象的實例,從而PositionedRectangle具備和Rectangle對象一致的原型對象。 */ PostionedRectangle.prototype = new Rectangle(); /** * 但不須要width和height屬性,須要刪除 */ delete PositionedRectangle.prototype.width; delete PostiionedRectangle.prototype.height; /** *設置PositionedRectangle的原型對象的constructor屬性的值是對象自身. */ PositionedRectangle.prototype.constructor=PositionedRectangle; /** *爲子類配置原型對象,且爲其添加方法。 */ PositionedRectangle.prototype.contains=function(x,y){ return (x>this.x && x<this.x +this.width && y>this.y && <this.y+ this.height); }
是否是想罵娘,說實話,我也很想!只是爲了建立一個子類而已,且如此的複雜,且徹底是技巧性的。緣由在於javascript中有類對象和原型對象兩種對象並存,帶來很是多的複雜性,必須深入理解原型對象繼承機制,才
可能有效實現類繼承。不過好在一些可以編譯成javascript語言的語言,可以幫助咱們,如coffeescript。
如今可使用它。
var r=PostionedRectangle(2,2,2,2); print(r.contans(3,3)); print(r.area()); print(r.x + ", "+r.y+", "+r.width+", "+r.height); print(r instanceof PostionedRectangle && r instanceof Rectangle && r instanceof Object);
用混入方式實現非繼承的擴展
說實話,第一遍看到實現類繼承的時候,幾乎想放棄,但javascript是目前惟一的可用於瀏覽器見解的語言,只能強忍「噁心」繼續看下去。利用javascript的靈活性,可採起組合方式實現相似繼承的效果。基於這樣一種原理:javascript函數式數據值(對象也是),能夠只是從一個類複製(或「借用」)函數用於另外一個類。下面看例子:
/** *從一個類Borrow方法到另一個類 *該類的構造函數必須有參數,內置類型如Object、Array、Date和RegExp不能枚舉,不能用這個方法借用。 */ function borrowMethods(borrowFrom,addTo){ var from=borrowFrom.prototype; //被借的原型對象 var to =addTo.prototype; //將被擴展的原型對象 for(m in from){ //循環原型的全部屬性 if(typeof from[m]!="function") continue; //忽略非函數 to[m]=from[m]; //借用該方法 } }
混入類(mixin class)
定義其餘類能夠借用的有用方法,除此外什麼都不作,這種類叫混入類。
下面ColoredRectangle類,從Rectangle類繼承矩形的功能更,而且從一個名爲Colored的混入類借用了一個方法。
//混入類Colored function Colored(c){this.color=c;} Colored.prototype.getColor=function(){return this.color;} function ColoredRectangle(x,y,w,h,c){ this.superclass(x,y,w,h); //調用父類構造函數 Colored.call(this,c); //借用Colored構造函數 } //實現繼承 ColoredRectangle.prototype=new Rectangle(); ColoredRectangle.prototype.constructor=ColoredRectangle; ColoredRectangle.prototype.superclass=Rectgangle; //借用方法 borrowMethods(Colored,ColoredRectangle);
這裏使用了superclass方式繼承父類,前面沒有進行說明,是由於這個方式只能使用一次,不能用於多層次繼承。
判斷對象類型
typeof運算符主要用於從對象類型中區分出基本類型,一旦肯定是對象而不是基本類型或者函數,就應使用instanceof運算符來判斷是哪一種對象類型:object、undefined、array、Function、Date等等。
鴨子類型識別(Duck Typing)
在javascript,若是它實現了一個類所定義的全部方法,它就是這個類的一個實例,即使它不是經過這個類的構造函數建立的。源於俗語:若是它走路像鴨子,叫起來像鴨子,那麼它必定是鴨子!識別的方式是逐一比較類的全部屬性(方法也是屬性)。
工具方法:defineClass()
最後犀牛書提供了一個很是實用的定義類的方法,能夠拿來使用,但基於學習目的,不建議在尚未徹底掌握js的狀況下使用。