一神郊、背景介紹
什么是面向對象編程?
Javascript是一種基于對象(object-based)的語言宅静,你遇到的所有東西幾乎都是對象章蚣。
今天主要內(nèi)容是兩個點:封裝和繼承。
二姨夹、知識剖析
如何把“屬性”(PROPERTY)和"方法"(METHOD)纤垂,封裝成一個對象?
1匀伏、 生成實例對象的原始模式
假定我們把貓看成一個對象洒忧,它有"名字"和"顏色"兩個屬性。
var Cat = {
name : '',
color : ''
}
現(xiàn)在够颠,我們需要根據(jù)這個原型對象的規(guī)格(schema)熙侍,生成兩個實例對象。
var cat1 = {}; // 創(chuàng)建一個空對象
cat1.name = "大毛"; // 按照原型對象的屬性賦值
cat1.color = "黃色";
var cat2 = {};
cat2.name = "二毛";
cat2.color = "黑色";
好了履磨,這就是最簡單的封裝了蛉抓,把兩個屬性封裝在一個對象里面。但是剃诅,這樣的寫法有兩個缺點巷送,一是如果多生成幾個實例,寫起來就非常麻煩矛辕;二是實例與原型之間笑跛,看不出有什么聯(lián)系。
2聊品、 原始模式的改進
我們可以寫一個函數(shù)飞蹂,解決代碼重復的問題。
然后生成實例對象翻屈,就等于是在調用函數(shù):
這種方法的問題依然是陈哑,cat1和cat2之間沒有內(nèi)在的聯(lián)系,不能反映出它們是同一個原型對象的實例伸眶。
3惊窖、 構造函數(shù)模式
為了解決從原型對象生成實例的問題,Javascript提供了一個構造函數(shù)(Constructor)模式厘贼。
所謂"構造函數(shù)"界酒,其實就是一個普通函數(shù),但是內(nèi)部使用了this變量嘴秸。對構造函數(shù)使用new運算符毁欣,就能生成實例售担,并且this變量會綁定在實例對象上。
比如署辉,貓的原型對象現(xiàn)在可以這樣寫族铆,
我們現(xiàn)在就可以生成實例對象了。
4哭尝、構造函數(shù)模式的問題
構造函數(shù)方法很好用哥攘,但是存在一個浪費內(nèi)存的問題。
請看材鹦,我們現(xiàn)在為Cat對象添加一個不變的屬性type(種類)逝淹,再添加一個方法eat(吃)。那么桶唐,原型對象Cat就變成了下面這樣:
還是采用同樣的方法栅葡,生成實例
表面上好像沒什么問題,但是實際上這樣做尤泽,有一個很大的弊端欣簇。那就是對于每一個實例對象,type屬性和eat()方法都是一模一樣的內(nèi)容坯约,每一次生成一個實例熊咽,都必須為重復的內(nèi)容,多占用一些內(nèi)存闹丐。這樣既不環(huán)保横殴,也缺乏效率。
5卿拴、 PROTOTYPE模式
Javascript規(guī)定衫仑,每一個構造函數(shù)都有一個prototype屬性,指向另一個對象堕花。這個對象的所有屬性和方法文狱,都會被構造函數(shù)的實例繼承。
這意味著航徙,我們可以把那些不變的屬性和方法如贷,直接定義在prototype對象上陷虎。
然后到踏,生成實例。
這時所有實例的type屬性和eat()方法尚猿,其實都是同一個內(nèi)存地址窝稿,指向prototype對象,因此就提高了運行效率凿掂。
三伴榔、常見問題
如何實現(xiàn)構造函數(shù)的繼承?
四纹蝴、解決方案
比如,現(xiàn)在有一個"動物"對象的構造函數(shù)踪少。
還有一個"貓"對象的構造函數(shù)塘安。
怎樣才能使"貓"繼承"動物"呢?
1援奢、 構造函數(shù)綁定
第一種方法也是最簡單的方法兼犯,使用call或apply方法,將父對象的構造函數(shù)綁定在子對象上集漾,即在子對象構造函數(shù)中加一行:
2切黔、 PROTOTYPE模式
第二種方法更常見,使用prototype屬性具篇。
如果"貓"的prototype對象纬霞,指向一個Animal的實例,那么所有"貓"的實例驱显,就能繼承Animal了诗芜。
代碼的第一行,我們將Cat的prototype對象指向一個Animal的實例埃疫。它相當于完全刪除了prototype 對象原先的值绢陌,然后賦予一個新值。但是熔恢,第二行又是什么意思呢脐湾?任何一個prototype對象都有一個constructor屬性,指向它的構造函數(shù)叙淌。如果沒有"Cat.prototype = new Animal();"這一行秤掌,Cat.prototype.constructor是指向Cat的;加了這一行以后鹰霍,Cat.prototype.constructor指向Animal闻鉴。更重要的是,每一個實例也有一個constructor屬性茂洒,默認調用prototype對象的constructor屬性孟岛。因此,在運行"Cat.prototype = new Animal();"這一行之后督勺,cat1.constructor也指向Animal渠羞!這顯然會導致繼承鏈的紊亂(cat1明明是用構造函數(shù)Cat生成的),因此我們必須手動糾正智哀,將Cat.prototype對象的constructor值改為Cat次询。這就是第二行的意思。
這是很重要的一點瓷叫,編程時務必要遵守屯吊。下文都遵循這一點送巡,即如果替換了prototype對象,那么盒卸,下一步必然是為新的prototype對象加上constructor屬性骗爆,并將這個屬性指回原來的構造函數(shù)。
3蔽介、 直接繼承PROTOTYPE
第三種方法是對第二種方法的改進淮腾。由于Animal對象中,不變的屬性都可以直接寫入Animal.prototype屉佳。所以谷朝,我們也可以讓Cat()跳過 Animal(),直接繼承Animal.prototype武花。
現(xiàn)在圆凰,我們先將Animal對象改寫:
然后,將Cat的prototype對象体箕,然后指向Animal的prototype對象专钉,這樣就完成了繼承。
與前一種方法相比累铅,這樣做的優(yōu)點是效率比較高(不用執(zhí)行和建立Animal的實例了)跃须,比較省內(nèi)存。
缺點是 Cat.prototype和Animal.prototype現(xiàn)在指向了同一個對象娃兽,那么任何對Cat.prototype的修改菇民,都會反映到Animal.prototype。
4投储、 利用空對象作為中介
由于"直接繼承prototype"存在上述的缺點第练,所以就有第四種方法,利用一個空對象作為中介玛荞。
F是空對象娇掏,所以幾乎不占內(nèi)存。這時勋眯,修改Cat的prototype對象婴梧,就不會影響到Animal的prototype對象。
我們將上面的方法客蹋,封裝成一個函數(shù)塞蹭,便于使用。
使用的時候嚼酝,方法如下
5浮还、 拷貝繼承
上面是采用prototype對象竟坛,實現(xiàn)繼承闽巩。我們也可以換一種思路钧舌,純粹采用"拷貝"方法實現(xiàn)繼承。簡單說涎跨,如果把父對象的所有屬性和方法洼冻,拷貝進子對象,不也能夠實現(xiàn)繼承嗎隅很?這樣我們就有了第五種方法撞牢。
首先,還是把Animal的所有不變屬性叔营,都放到它的prototype對象上屋彪。
然后,再寫一個函數(shù)绒尊,實現(xiàn)屬性拷貝的目的畜挥。
這個函數(shù)的作用,就是將父對象的prototype對象中的屬性婴谱,一一拷貝給Child對象的prototype對象蟹但。
使用的時候,這樣寫:
五谭羔、編碼實戰(zhàn)
六华糖、擴展思考
面向過程到面向對象思維如何轉變?
當我們習慣了面向過程編程時瘟裸,發(fā)現(xiàn)在程序過程中到處找不到需要面向對象的地方客叉,最主要的原因,是思維沒有轉變话告。程序員通常在拿到一個需求的時候十办,第一個反應就是如何實現(xiàn)這個需求,這是典型的面向過程的思維過程超棺,而且很快可能就實現(xiàn)了它向族。而面向對象,面對的卻是客體棠绘,第一步不是考慮如何實現(xiàn)需求件相,而是進行需求分析,就是根據(jù)需求找到其中的客體氧苍,再找到這些客體之間的聯(lián)系夜矗。
七、更多討論
討論點一让虐、如何不使用構造函數(shù)實現(xiàn)繼承紊撕?
討論點二、還有哪些實現(xiàn)封裝的方法赡突?
討論點三对扶、還有哪些實現(xiàn)繼承的方法区赵?