學習Javascript人,大多聽說一句話叫js里面一切都是對象。我剛開始接觸javascript面向對象編程時候,挺亂的,我當時習慣性的把PHP的面像對象思想套用在js上面,其實js的面向對象與傳統的面向對象還是有很多區別的。這里就不再去講解基礎的面向對象是什么了,看這篇文章就默認大家都知道面向對象的概念。
首先,在目前的js版本中,依然沒有引入class這個關鍵詞,js里面沒有類的概念,其他語言在實例化一個對象的時候,都是使用new 類名來得到實例,而js由於沒有class,因此它的面向對象也可以理解為是一種模擬的方式。首先我們來說一下js里面的函數,js里面都函數有一個特點,就是所有的函數都有返回值,如果我們沒有手動編寫 return。那么函數會返回一個underfind,如果寫了,則返回你寫的值。這種返回值是在正常的函數調用情況下出現的。比如:
function a(){
return 123;
}
a();
此時返回123。
function a(){
}
a();
此時返回underfind。
那么,我們在調用js里面的函數時候,其實不止這一種方式,常用的還用使用call或者apply方法也可以使函數執行,這個以后再說。除此之外,我們還可以通過 new的方式來調用函數,new在js里面其實是一種運算符,但凡函數是通過它來調用的,返回值就會發生變化。如果我們在函數中沒有寫返回值或者返回值寫的不是對象類型的數據,那么這個函數都會返回一個空對象,如果我們寫的返回值是一個對象,那么則返回我們寫的對象。所以只要是通過new來調用函數,返回值就變成了對象。
js也充分利用了這一點,來模擬傳統的面向對象,我們來看一個例子:
function Obj(){
this.name='小紅';
this.age='24';
}
var poeple = new Obj();
根據我們上面說的,當我們使用new去調用函數時候,返回對象,因此我們可以得知people其實就是一個對象了。我們看到上面的代碼中this.name='小紅',this.age='24',這個this其實是什么呢?其實這個this就是我們返回的對象,也就是說this就是people,如果不能理解,只需記住,這是js的特點。我們接着看:
function Obj(){
this.name='小紅';
this.age='24';
}
var poeple = new Obj();
alert(people.name);
alert(people.age);
運行上述代碼以后,分別彈出了小紅和24,這更加確切的說明this就是people.
但是這個this也不一定只是people,確切的說這個this是誰具體要看是誰在調用這個函數,看例子:
function Obj(){
this.name='小紅';
this.age='24';
}
Obj();
當我這樣去調用的時候,這個this指向的是誰呢?其實js是運行在瀏覽器中的,瀏覽器中有一個頂層對象叫做window,所有的變量和函數其實都是掛在他下面的,看例子:
Obj();
window.Obj();
這兩種寫法的效果是一樣的,此時沒有通過new來調用,其實就是一個普通的函數,而這個函數的調用者是window,因此這個this指向window.
所以說this的指向不是固定的,要看具體的調用方式以及是誰在調用。
我們一般把這種用來返回的對象的函數稱作構造函數,他的作用就是用來創建對象的,一般為了區分構造函數與普通函數的區別,構造函數的首字母會大寫。我早期學習的時候,就把這里的構造函數與其他語言里的class弄混淆了,其實他們是不同的東西。
第二個就是js的面向對象同樣有繼承的功能,只是它的繼承與傳統的繼承方式也不一樣,我個人認為也是一種模擬。首先說明一件事情,在js里面所有的對象都有一個叫做__proto__的屬性,所有的函數都有__proto_的屬性和prototype的屬性,prototype屬性對應的值是一個對象,這個對象可以保存很多東西的,中文名稱叫做原型對象,看例子:
function Obj(){
this.name='小紅';
this.age='24';
}
Obj.prototype.job='老師';
var poeple = new Obj();
alert(people.job);// 彈出 老師
剛才已經說過,此時的people就是一個對象,而Obj是一個函數,people是Obj的一個實例對象,他們之間肯定是有關系的,既然people是一個對象,那么他肯定有_proto_屬性,這個屬性其實指向了Obj的prototype,再詳細說一下,people的__proto__保存的是一個地址,這個地址指向的是people的構造函數Obj的prototype屬性。所以程序運行到people.job的時候,people這個對象在自己的屬性里面開始尋找job這個東西,但是木有找到,於是接下來他就找到了自己的__proto__,順着里面的地址找到了Obj.prototype,在這里發現了job,然后呢,他就把job的值給彈了出來。這一連串的動作專業術語叫做原型鏈查找,那為什么要說這個呢,主要是因為js的繼承有一種方式就叫做原型繼承。那上面已經把對象和構造函數的在原型方面的聯系說清楚了。那么接下來我們就說一下js的原型繼承,看例子:
function a(){
this.name='小紅';
this.age='24';
}
function b(){
this.job='小紅';
this.sex='男';
}
b.prototype=new a();
var c = new b();
alert(c.name);//小紅
以上代碼,做了一個功能,把b的原型對象修改成了a的實例對象,這時b的實例對象去訪問name屬性時候,首先在自身查找,沒有發現,於是去他的構造函數b的原型里面去查找,而此時b的原型就是一個a的實例對象,a的實例對象當然有name和age屬性了,因此就訪問到了。這種方式成為原型繼承,有一個小問題,就是這樣直接把對象賦值給prototype,修改了prototype里面的constructer屬性,constructer屬性后面再說。
除了原型繼承以外,還有一些方式也可以實現繼承,最常用的就是call和apply方法,解釋這個問題還需要了解另外一個特性,我們上面已經看到了,call方法可以用來調用函數,其實他的功能不止於此,最核心的功能就是改變this指向,我們來看例子:
此時,對象c擁有 name age self三個屬性,前面兩個就是繼承下來的了。
其實,真實的編寫過程,往往把上述兩種繼承方式同時使用,當然還有一些其他的繼承方式,大家可以自己去網上查查。
這里說一下,上面那種原型繼承法有一點點問題,直接把對象賦值給原型會導致原型里面的constructor出現錯誤,這個constructor實際上用來保存對象的構造函數的,也就是說一個對象是由那個構造函數實例化而成的,那么這個constructor就代表那個構造函數。舉例:
function a(){
this.name='小紅';
this.age='24';
}
var b = new a();
alert(a.constructor); // 打印出來的就是整個函數
如果直接把對象賦值給原型,這個constructor值就會被修改,所以,為了保險起見,很多人都會手動加上一句 a.prototype.constructor = a;
這里沒有介紹更多的面向對象的東西,寫的過程中也有遇到疑惑的,如果有錯誤的話,請大家多多指教!