標籤:
1.物件導向的編程1.1.什麼是物件導向編程
物件導向編程:即是把能夠完成獨立完成一部分功能的代碼封裝在一起,組成一個類。
舉個例子來說:
這裡有一把槍, 槍的種類很多,有步槍,機關槍,阻擊槍....。但是無論怎麼說,這些都是槍的概念,如果把這種抽象的概念剝離出來,就是我們經常說的“類”。那麼槍有什麼特點呢? 威力大小,型號,長度,子彈型號,能承載子彈的數量,槍口半徑......ok! 這些一切的一切都是為了描素槍,把這些抽象出來,就組成了“槍類”的屬性。槍又能幹什麼呢? 瞄準,開火,....這些描素都是槍的功能-----把這些抽象出來,即組成了一個類的方法。
所以,總體來說,物件導向的編程即是把一個程式模組化,每個模組承載一部分功能,各個模組協同合作,維持程式的正常執行;
而所謂的類的組成,無外乎三個部分:這個類的名稱(對應著例子中的“槍”),這個類的屬性(對應著特點),這個類的方法(對應著功能)。
就像我們描素一個人一樣,無外乎,描素一個人的特點以及人的能力。所以,現實生活中的人,在程式中也可以抽象成類。
1.2.類,對象,執行個體的關係
- 類:是一個抽象概念,是對某一類相似對象的抽象。
- 對象:是類的一個執行個體化,因為類是一個抽象的概念,所以,在使用時必須落實到實物的身上。那麼,對象就作為載體來完成某項功能。
- 執行個體:和對象是一個概念。一般說一個類的執行個體,指的就是這個類的某個對象。
舉個例子來說明三者之間的關係:
//1.Person是一個類的名字,定義的是一個人,對這個人的描述一般就是姓名,年齡。
var Person = function (name, age) {
//Person類的屬性 this.name = name; this.age = age;}
//Person類的方法Person.prototype.greet = function () { console.log(‘hello‘);}
//這是一個類的執行個體化過程,lisi這裡就是Person類的一個對象,也可以說其是Person類的一個執行個體
var lisi=new Person("lisi",18);
1.3.物件導向的四個特點
- 封裝. 所謂的封裝就是把一個類的對象的屬性和方法封裝在類的內部。封裝的好處就是:類與類之間的屬性和方法相互獨立,互不干擾。
- 繼承. 所謂的繼承就是指的是一個類可以派生自另外一個類。比如,圖形類,可以派生出三角形,正方形,圓....
- 重載. 重載就是指一個類的方法可以名字可以相同(JS不支援重載).第二部分會給出解釋。
- 多態.多態指的是父類的方法,子類可以重寫該方法。那麼,子類在調用該方法時調用的會是子類的方法。
請記住:物件導向,所有的一些都是為了代碼的複用。
2.物件導向的四個特點在JS中的實現 2.1.JS中的封裝
JS類的封裝即是把類的屬性和方法封裝在類的內部. 如果只是簡單的實現封裝,那麼可以有多種方法。比如下面的兩種
//第一種方法
var Person = function (name, age) { this.name = name; this.age = age; this.greet = function () { console.log(‘hello‘); }}
//第二種方法var Person = function (name, age) { this.name = name; this.age = age;}Person.prototype.greet = function () { console.log(‘hello‘);}
這兩種方法,雖然在使用效果上是一致的,但是在第一種方法中,每次new 一個對象的時候都要為該對象添加一個函數greet----這樣就沒有做到代碼的複用。所以在使用的時候,一般都是使用第二種方式---也就是所謂的組合式建立。所以一般我們也推薦用第二種方式。
2.2.JS中不存在重載
什麼是重載呢? 重載的概念來源於強型別預言(C++,java,C#)中。我們先來看一些java中的重載
class Person{ //java語言, 定義一個Person類,該類中存在greet方法的重載。 public String name; public int age; Person(String name,int age){ this.name=name; this.age=age; } public void greet(){ System.out.println("I am "+ this.name); } public void greet(String message){ System.out.println("I am "+ this.name+ "\n This is your"+message); }}
所謂的重載,就是一個同一個方法名在一個類中被出現了多次。那麼在該方法被調用的時候,編譯器如何區分具體調用哪個方法呢?
在強型別語言中,編譯器先根據函數的名字選擇函數,然後在根據調用時,形參和實參的類型,形參的個數和實參的個數是否一致來區分一個函數。
那麼,問題來了....JS中的解譯器是符合區分一個函數呢? ok...JS中解譯器只是根據函數的名稱來選擇函數,而函數的形參並不在考慮的範圍----因為在編譯時間無法根據確定形參的類型,更無法確定實參的類型。
既然,JS不支援重載,那麼如果一個函數被重寫了,會出現什麼情況呢?
var Person = function (name, age) { this.name = name; this.age = age; this.greet = function () { console.log(‘hello‘); }}var Person = function (name, age) { this.name = name; this.age = age;}Person.prototype.greet = function () { console.log(‘我被覆蓋了‘);}Person.prototype.greet = function (message) { console.log("我是重寫的方法");}var person=new Person("zhangsan",18);person.greet(); //我是重寫的方法
根據上面的例子,可以看出,無論函數的參數是什麼,只要函數同名,那麼被調用的肯定是最後一次被寫的同名函數。
2.3.JS中的繼承
繼承這個概念的來源也是物件導向的編程。JS引薦強型別預言中的繼承做到這一點。所以我們要從強型別語言中的繼承來類推---JS中為什麼要這麼設計。
2.3.1.強型別語言中繼承的實現
在強型別語言中,在假設有兩個類 A 、B....A是B的父類。實現如下:
class A{//父類的建構函式 protected int x; A(int x){ this.x=x; }}class B extends A{ protected int y; B(int x,int y){//子類的建構函式 super(x); //在子類的建構函式中,第一句話總是先調用父類的建構函式,如果不寫 則預設調用super();如果父類中不存在無參建構函式,則編譯時間會報錯。 this.y=y; }
public String getPoint(){
return "("+this.x+","+this.y+")"; //返回座標(x,y)
}
}
從上面的這些我們可以看出什麼呢? 就是對象初始化的順序...先初始化父類,在初始化子類。
初始化的時候順序為: 父類的屬性----》父類的方法-----》子類的屬性-----》子類的方法。(我們這裡講的是排除了類中待用資料和方法來說,因為待用資料和方法的初始化,在類第一次被載入的時候就已經初始化完畢)
下面我們看下,JS中是怎麼實現和上述一樣的功能的...
var A = function (x) { this.x = x;}var B = function (x, y) { A.call(this, x); //相當於第一種的super()函數。 this.y = y;}
//實現繼承function extend(subClass, superClass) { var prototype = Object.create(superClass); subClass.prototype = prototype; subClass.constructor = subClass;}extend(B, A);B.prototype.getPoint = function () { return ‘(‘ + this.x + ‘,‘ + this.y + ‘)‘;}
上面這兩段代碼,撇開語言的特性來說,他們實現的功能是等效的。只是第一種玩的是思想,第二種玩的是技巧。
OK!下面我們開始詳解JS的設計者為了JS語言能實現繼承所做的努力。
2.3.2.JS語言支援繼承的原理。
所有的函數均有一個prototype屬性。就是這個屬性幫我們做到了一些,首先要認識到一點這個屬性是一個對象。
用上面我們建立的一個Person函數詳解,那麼這個函數的prototype屬性如下表示:
這是這個函數在剛開始被初始化時候的固有形式,後來執行了一句
Person.prototype.greet = function () { console.log(‘hello‘);}
在這句執行完畢的時候,Person.prototype變化為
| Person prototype |
| constructor |
指向Person函數 |
| greet |
(greet函數) |
解釋了這麼多,貌似並沒有解釋繼承是怎麼實現的是吧....別慌...慢慢來!!!
來看一下,當一個函數被執行個體化的時候發生了什嗎?
var lisi=new Person("lisi",18); //看看Person執行個體化的對象發生了什嗎?
到這裡,我們看到了吧..當用new建立一個建構函式的對象的時候。這個對象會有一個【【__proto__】】內建屬性,指向函數的prototype。------這就是對象lisi傳說中的原型對象。
一個函數只有一個原型(prototype),這個函數在用new調用的時候會把這個原型賦值給當前對象的__proto__屬性。
當查詢一個對象的屬性的時候,首先查詢對象本身的屬性,如果沒有找到則根據對象__proto__屬性層層向上尋找。
所以一切的一切都歸咎於,只要們修改一個函數的prototype屬性,那麼就可以實現繼承。
下面圖解,繼承B繼承A的過程。
//1. A類,和B類的建構函式
var A = function (x) { this.x = x;}var B = function (x, y) { A.call(this, x); //相當於第一種的super()函數。 this.y = y;}
//2.修改B的prototype使其繼承A
//實現繼承function extend(subClass, superClass) { var prototype = Object.create(superClass); subClass.prototype = prototype; subClass.constructor = subClass;}extend(B, A);B.prototype.getPoint = function () { return ‘(‘ + this.x + ‘,‘ + this.y + ‘)‘;}
如此,便實現B類繼承A類...關鍵點就在函數的prototype屬性上。----在下一篇中會詳解函數的prototype。
JS中實現多態
何謂多態?
首先一定要強調一點,只有在繼承存在的情況下才可能出現多態? 這是為什麼呢..因為多態指的是子類覆蓋父類的方法..這種情況就是所謂的多態。
在java中的多態
public class Test { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub B b=new B(1,2); String result=b.getPoint(); System.out.println(result); }}class A{ protected int x; A(int x){ this.x=x; } public String getPoint(){ return "我是父類"; }}class B extends A{ protected int y; B(int x,int y){ super(x); this.y=y; }
//多態,父類覆蓋的方法 public String getPoint(){ return "我是子類"; }}
//輸出結果 :我是子類
在JS中的多態情況,也是指的的子類的方法覆蓋父類的方法。 上面的功能在JS中是這麼實現的。
var A = function (x) { this.x = x;}A.prototype.getPoint = function () { return ‘我是子類‘;}var B = function (x, y) { A.call(this, x); //相當於第一種的super()函數。 this.y = y;}//實現繼承function extend(subClass, superClass) { var prototype = Object.create(superClass); subClass.prototype = prototype; subClass.constructor = subClass;}extend(B, A);B.prototype.getPoint = function () { return ‘我是子類‘;}var b = new B(1, 2);b.getPoint();
//輸出結果 :我是子類
在上述代碼執行完畢後,函數B的結構
B類在執行個體化的時候,B類的對象會擁有一個內部屬性指向 B.prototype.當該執行個體調用函數的時候,會先在該對象內部查詢該函數是否存在,如果不存在則沿著__proto__屬性查詢原型對象,即B.prototype。如果找到此函數,則停止查詢,否則會接著沿著__proto__屬性所指向的對象,一直找到最上級為止。
JavaScript系列----物件導向的JavaScript