面向對象編程:便是把可以完成獨立完成一部分功能的代碼封裝在一塊兒,組成一個類。javascript
舉個例子來講:java
這裏有一把槍, 槍的種類不少,有步槍,機關槍,阻擊槍....。可是不管怎麼說,這些都是槍的概念,若是把這種抽象的概念剝離出來,就是咱們常常說的「類」。那麼槍有什麼特色呢? 威力大小,型號,長度,子彈型號,能承載子彈的數量,槍口半徑......ok! 這些一切的一切都是爲了描素槍,把這些抽象出來,就組成了「槍類」的屬性。槍又能幹什麼呢? 瞄準,開火,....這些描素都是槍的功能-----把這些抽象出來,即組成了一個類的方法。編程
因此,整體來講,面向對象的編程便是把一個程序模塊化,每一個模塊承載一部分功能,各個模塊協同合做,維持程序的正常執行;模塊化
而所謂的類的組成,無外乎三個部分:這個類的名稱(對應着例子中的「槍」),這個類的屬性(對應着特色),這個類的方法(對應着功能)。函數
就像咱們描素一我的同樣,無外乎,描素一我的的特色以及人的能力。因此,現實生活中的人,在程序中也能夠抽象成類。this
舉個例子來講明三者之間的關係:spa
//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);
請記住:面向對象,全部的一些都是爲了代碼的複用。prototype
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----這樣就沒有作到代碼的複用。因此在使用的時候,通常都是使用第二種方式---也就是所謂的組合式建立。因此通常咱們也推薦用第二種方式。3d
什麼是重載呢? 重載的概念來源於強類型預言(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(); //我是重寫的方法
根據上面的例子,能夠看出,不管函數的參數是什麼,只要函數同名,那麼被調用的確定是最後一次被寫的同名函數。
繼承這個概念的來源也是面向對象的編程。JS引薦強類型預言中的繼承作到這一點。因此咱們要從強類型語言中的繼承來類推---JS中爲何要這麼設計。
在強類型語言中,在假設有兩個類 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語言能實現繼承所作的努力。
全部的函數均有一個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調用的時候會把這個原型賦值給當前對象的內置屬性。
當查詢一個對象的屬性的時候,首先查詢對象自己的屬性,若是沒有找到則根據對象內置屬性層層向上查找。
因此一切的一切都歸咎於,只要們修改一個函數的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的prototype
//實現繼承 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。
何謂多態?
首先必定要強調一點,只有在繼承存在的狀況下才可能出現多態! 這是爲何呢..由於多態指的是子類覆蓋父類的方法.....子類覆蓋父類的方法,這種狀況就是所謂的多態。
在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.當該實例調用函數的時候,會先在該對象內部查詢該函數是否存在,若是不存在則沿着內置屬性查詢原型對象,即B.prototype。若是找到此屬性,則中止查詢,不然會接着沿着內置屬性所指向的對象,一直找到最上級爲止。