前言:你們都知道,OOP有三大特性:封裝,繼承,多態。下面是本身對這三個特性的理解:java
封裝:把屬性與方法整合到某種數據類型中。目的是讓類的使用者按類的編寫者的意願去使用類。在封裝過程當中會通常會作兩件事:閉包
① 隱藏內部實現 ② 對外提供接口(訪問權限控制)。ide
繼承:共享父類的屬性與方法函數
多態:不一樣對象執行相同的操做,能夠產生不一樣的結果。關於多態要注意兩點:測試
① 在子類以父類的形式存在時,不能使用子類的屬性與方法
② 子類在工做時,採用本身的實現方式。this
下面咱們以java爲例子看看OOP的三個特性。spa
/** * 定義類:Animal */ public class Animal { private String cate; public String getCate() { return cate; } public void setCate(String cate) { this.cate = cate; } public void shout() { System.out.println("Animal shouted"); } } /** * 定義類:Dog,並讓其從Animal繼承 */ class Dog extends Animal { @Override public void shout() { System.out.println("dog shouted"); } } /** * 定義類:Collie,並讓其從Dog類繼承 */ class Collie extends Dog { @Override public void shout() { System.out.println("collie shouted"); } public void graze() { System.out.println("collie graze"); } }
//測試代碼 public class OOPTest { public static void main(String[] args) { //封裝 Animal animal = new Animal(); animal.shout();//Animal shouted //繼承: Dog中並無聲明cate域,也沒有對應的setter/getter Dog dog = new Dog(); dog.setCate("dog"); System.out.println(dog.getCate());//dog //多態 Animal dog2 = new Collie(); dog2.shout();//collie shouted //dog2.graze();error! 在子類以父類的形式存在時,不能使用子類的屬性與方法 ((Collie)dog2).graze();//進行向下轉型後能夠運行,打印出:collie graze } }
一: OOP在JS中的實現prototype
但對於Js而言,目前尚未實現class關鍵字,也沒有private,protected,public權限修辭符。但咱們可使用閉包來實現封裝:指針
/** * 定義Animal類型 * @returns {Animal} * @constructor */ function Animal() { var _cate; //防止直接調用Animal構造函數在window上新增屬性 if(this instanceof Animal) { this.setCate = cateSetter; this.getCate = cateGetter; this.shout = shout; } else { return new Animal; } //使用函數名cateSetter方便理解 function cateSetter(cate) { _cate = cate; } function cateGetter() { return _cate; } function shout() { console.log('Animal shouted'); } }
//使用: var animal = new Animal(); animal.shout();//animal shouted //直接不能訪問到cate屬性,只有經過對外提供的方法(setCate/getCate)去訪問 animal.setCate('animal~~'); console.log(animal.getCate());//animal~~
JS中的繼承是經過prototype來實現的,如今新增Dog類型,並從Animal繼承:code
/** * 定義Dog類型,並讓其從Animal繼承 * @constructor */ function Dog() {} Dog.prototype = new Animal(); //讓instanceof運行符能夠正常工做 Dog.prototype.constructor = Dog;
//測試: var dog = new Dog(); //dog具備了方法shout, cate的getter/setter dog.setCate('dog'); console.log(dog.getCate());//dog dog.shout();//animal shouted console.log(dog instanceof Dog);//true console.log(dog instanceof Animal);//true
接着定義類型Collie,並實現多態:
/** * 定義Collie類型,並讓其從Dog繼承 * @constructor */ function Collie(){} //讓Collie從Dog繼承 Collie.prototype = new Dog; Collie.prototype.constructor = Collie; //爲Collie新增graze方法 Collie.prototype.graze = function() { console.log('collie graze'); } //重寫Collie的shout方法 Collie.prototype.shout = function() { console.log('collie shouted'); }
//測試 var collie = new Collie(); collie.setCate('collie'); console.log(collie.getCate());//collie console.log(collie instanceof Collie);//true console.log(collie instanceof Dog);//true console.log(collie instanceof Animal);//true //多態:相同的方法,產生了不一樣的行爲 collie.shout(); //collie shouted
完整的OOP實現代碼:
/** * 定義Animal類型 * @returns {Animal} * @constructor */ function Animal() { var _cate; //防止直接調用Animal構造函數 if(this instanceof Animal) { this.setCate = cateSetter; this.getCate = cateGetter; this.shout = shout; } else { return new Animal; } //使用函數名cateSetter方便理解 function cateSetter(cate) { _cate = cate; } function cateGetter() { return _cate; } function shout() { console.log('animal shouted'); } } /** * 定義Dog類型,並讓其從Animal繼承 * @constructor */ function Dog() {} Dog.prototype = new Animal(); //讓instanceof運行符能夠正常工做 Dog.prototype.constructor = Dog; /** * 定義Collie類型,並讓其從Dog繼承 * @constructor */ function Collie(){} //讓Collie從Dog繼承 Collie.prototype = new Dog; Collie.prototype.constructor = Collie; //爲Collie新增graze方法 Collie.prototype.graze = function() { console.log('collie graze'); } //重寫Collie的shout方法 Collie.prototype.shout = function() { console.log('collie shouted'); }
二: 缺點
這種構造函數(閉包)+prototype的實現的缺點:
1. 使用閉包模擬私有屬性時,形成同一類型的多個實例共享一個相同閉包變量(Dog.prototype = new Animal() 只生成了一個閉包變量)
2. 每次實例化一個子對象時,都先要實例化一個父對象
3. 不能在子對象上調用父對象上的同名方法
4. 引用類型的共享Bug(在prototype上面的引用類型都會有這個問題,由於各個function的prototype是一個指針,實際的prototype對象在堆中只有一分內存分配)
5. 封裝不優雅,很散亂
缺點1的測試代碼:
var dog = new Dog(); var dog2 = new Dog(); //dog1,dog2共享閉包變量_cate,明顯這不合適 dog.setCate('dog1'); console.log(dog.getCate());//dog1 dog2.setCate('dog2'); console.log('+++'+dog.getCate());//dog2 console.log('+++'+dog2.getCate());//dog2