相信每個學習過其他語言的同學再去學習JavaScript時就會感覺到諸多的不適應,這真是一個顛覆我們以前的編程思想的一門語言,先不要說它的各種數據類型以及表達式的不同了,最讓我們頭疼,恐怕就是面向對象的部分了,在JavaScript中,是沒有給定一個創建對象的關鍵詞的,它不像Java中一個class就可以創建一個對象,在JavaScript中,對象是一個十分松散的的key-value對的組合,通常,我們在創建對象時,可以通過{}來直接生成一個對象,就像我們之前所學的,對象中有屬性,有行為,這里我們稱為方法,那我們就先來創建一個對象:
var zhangSan ={name:"張三",age:14}
這就是一個簡單的對象,這種方式的優點是簡單直接,一眼就可以看出來他的屬性和方法但是,這種方法的缺點也很明顯,當我們要創建多個對象時,我們就需要一個一個的去創建,一個一個的去賦值,這是十分麻煩而且也不太顯示的方法,那么我們可以用工廠法來創建:
1 function createPeople(name,food){
2 var people = new object(); 3 people.name=name; 4 people.eat=function(food){ 5 alert(food); 6 } 7 return people; 8 } 9 var zhangSan=createPeople("zhangSan","豆腐"); 10 zhangSan.eat();//這時會彈出豆腐.
這種方法可以創建多個對象,用個俗一點的話說,就是你想要造多少個人就可以造多少個人,哈哈,那么你以為你這是很好的創建對象的方法了嗎?錯了,現在我們又有一個問題,就是,我們現在用的這種工廠方法,你並不知道你創建的是一個什么樣的類,你創建的people歸根底還是由new object()來創建的,和用字面量的方式定義一個類的方法並沒有本質上的區別,同樣的,我也可以由這個方法創建一條狗,一只貓,一頭大象,一條板凳,等等,這樣子,類的意義還有什么存在的必要呢,這時,就出現了用構造方法創建類的方法見代碼2:
1 function createPeople(name,age,food){
2 this.name=name; 3 this.age=age; 4 this.food=food; 5 this.eat=function(){ 6 alert(this.name+"愛吃"+this.food; 7 } 8 } 9 var zhangSan = new createPeople("張三",13,"豆腐"); 10 zhangSan.eat();//這是會彈出張三愛吃豆腐這句話
采用這種方法,我們可以避免了上邊說的那種不知道創建的是什么類型的對象的那種情況,這里我們要來介紹一下構造方法來創建對象,如果不加new,createPeople()只是一個非常普通的函數,但是在它前邊加上new之后,這一切都會變得不同,代碼2中第九行的那句話可以分解為兩句話來理解:
1 var zhangSan={};//第一步首先創建一個空對象, 2 createPeople.apply(zhangSan arguments);//這一步將函數中的this綁定到zhangSan上,然后就是執行函數中的代碼
好了,采用構造函數創建對象,我們可以將這個函數理解成一個類,由這個類可以創造屬於它的對像,而不是像上邊的工廠方法那樣每個對象都沒有明確的分類,了解javascript內存管理機智的我們應該都知道,在Javascript中,你每次創建一個對象,堆中就要為這個對象的方法和屬性分配一個內存空間,但是對於很多對象來說,它們的屬性大都不盡相同,但是很多時候它們的很多方法都是相同的,不同的只是傳入的參數的不同而已,這樣子是很浪費我們的內存的,這時候,我們就先去了解一下對象的原型鏈:
每個函數內都有一個prototype屬性,這個prototype屬性執行它們的prototype對象,而每一個函數的protocoltype對象中都有一個constructor屬性,這個屬性指向這個函數本身,
所以問題就來,這是一個很繞口的問題,那么我們就來張圖理解一下:
那你說這跟我創建的每一個對象上的方法有神馬關系呢?當然有關系了,當我們使用new一個構造方法來創建一個對象的時候,我們創建的那個對象中,也會有一個_proto_屬性,這個屬性指向構造函數的原型對象,也就是說,在我們對象實例和構造函數之間,它們都指向對象原型,我們每一個對象中都可以繼承構造函數原型對象中的屬性和方法.那么這就引出了我們要說的內容,就是將我們每個對象都需要的方法放到我們的prototype對象中:
1 function createPeo(userName,hobby){
2 this.userName=userName; 3 this.hobby=hobby; 4 createPeo.prototype.eat=function (){//在對象原型上定義函數,來節省內存; 5 alert(this.userName+"喜歡吃"+this.hobby); 6 } 7 } 8 var shang = new createPeo("張珊","炒面"); 9 var liSi = new createPeo("李四","擼串");//這樣就可以極大的節省了我們的內存,而且每一個對象都能調用到我們的方法 .
好了,現在我們已經成功的創建了一個類,並且由這個類創建了我么所需要的對象,那么我們都知道,有了類,有了對象,我們總是會下意識的去背了起來,累的三大特征:封裝,繼承,多態,那么在JavaScript中,類的封裝是怎么來實現的呢?這個在明天講述閉包的時候我們再進行討論,今天我們先來討論一下類中都有的繼承,在我們JavaScript中,類的繼承並不像Java中的那樣用一個class...extend就行了,在JavaScript中,我們並沒有一個class繼承的概念,我們用的是很隨意的一個原型鏈的執行繼承,就是通過prototype屬性來指向想要繼承的對象的原型對象,那么我們就開始想,那么我直接用prototype屬性指向不就行了嗎?
1 function Dog(name,age){
2 this.name=name; 3 this.age=age; 4 } 5 function LittleDog(){} 6 LittleDog.prototype=Dog.prototype;//你以為這樣就完成了原型繼承,只不過,這樣子直接指向原型鏈的話,你在修改子類的時候,相應的就會修改父類.並且,既然他兩個都指向一個原型,那你直接用Dog來創建不就完了了嗎,這就失去了繼承的意義.
所以,直接將原型鏈指向父類是不對的,說到這里,我們就可以用一個空函數來作為中轉完成原型鏈的繼承:
function Dog(name,age){
this.name=name; this.age=age; Dog.prototype.eat=function(){alert("我今年"+this.age+"歲"); } } function LittleDog(name,age){ this.name=name; this.age=age; } function F(){} F.prototype=Dog.prototype; LittleDog.prototype=new F(); LittleDog.prototype.constructor=LittleDog; var xiaoGou=new LittleDog("旺財",13); xiaoGou.eat();//這時用小狗調用父類中的方法,返回一個彈框,說明繼承成功.
通過這種中間函數來完成類的繼承.它並沒有改變原有Dog中的原型鏈條,同時也完成了繼承,如果想要在LitteDog中添加原型方法,就可以在new F()中創建方法和屬性,當然,我們也可以將這種繼承方法封裝成一個函數,這樣,我們程序的執行效率和美觀程度就大大的提升了:
1 function inherits(child,parent){
2 var f = function(){} 3 f.prototype = parent.prototype; 4 child.prototype = new f(); 5 child.prototype.constructor=child; 6 }//這是一個封裝函數,再進行類的繼承時,可以用這個函數來套用.
在類的繼承中,除了我們上邊說的這種原型繼承,還有類繼承,以及類繼承與原型繼承的混用,那么我將在下一章給大家講解.本博文是博主自創,如果轉載請說明出處,謝謝!在下學藝不精,文中如果有什么錯誤還請高手指正,多謝!