第158天:面向對象入門
什麽是對象
我們先來看高程三中是如何對對象進行定義的
"無序屬性的集合,其屬性可以包括基本值、對象或者函數",對象是一組沒有特定順序的的值。對象的沒個屬性或方法都有一個俄名字,每個名字都映射到一個值。
簡單來理解對象就是由屬性和方法來組成的
面向對象的特點
-封裝
對於一些功能相同或者相似的代碼,我們可以放到一個函數中去,多次用到此功能時,我們只需要調用即可,無需多次重寫。
在這裏我們可以理解為創造對象的幾種模式:單例模式,工廠模式,構造函數模式,原型模式等。
-
繼承
子類可以繼承父類的屬性和方法
- 多態(重載和重寫)
- 重載:嚴格意義上說js中沒有重載的功能,不過我們可以通過判斷函數的參數的不同來實現不同的功能來模擬重載。
- 重寫:子類可以改寫父類的屬性和方法
javascript中的封裝
- 單例模式
小王在一個小公司,就自己一個前端,所以他寫js都是這樣的
1 var a = 1; 2 function getNum(){ 3 return 1; 4 }
後來公司又招了個前端小明,於是變成他們2個一起寫同一個js了。一天小王發現自己寫的getNum方法出問題了,原來是小華寫的js中也有個getNum的函數,代碼合並後把他的覆蓋掉了,於是便找小華理論去,經過一番妥協後,兩人都把自己的代碼改了改
1 var xiaoming = { 2 num:1,3 getNum:function(){ 4 return 1; 5 } 6 } 7 8 var xiaohua = { 9 num:2, 10 getNum: function(){ 11 return 2; 12 } 13 }
這就是我們所謂的單例模式(命名空間)
我們把描述同一個事物的方法或者屬性放到同一個對象裏,不同事物之間的方法或者屬性名相同相互也不會發生沖突。
單例模式的優劣
-
使用單例模式,我們可以實現簡單的模塊化開發
1 var utils = { 2 getCss:function(){ 3 //code 4 }, 5 getByClass:function(){ 6 //code 7 }, 8 setCss:function(){ 9 //code 10 } 11 }
- 我們可以把自己寫好的工具方法放到一個單獨的js文件中,然後直接引入即可。
- 避免了全局變量名的沖突
需要註意的是,我們在引入各個模塊的時候,需要註意引入的順序,引入順序是按照各模塊之間的相互依賴進行前後排列的; - 缺點:
- 單例模式只是一定程度上避免了變量名的沖突,但並不能徹底解決此問題,而且在不同的對象下,我們可能會有很多功能相同的代碼,最終造成大量的冗余代碼。
- 單例模式讓每個對象有了自己獨立的命名空間,但是並不能批量生產的問題,每一個新的對象都要重新寫一份一模一樣的代碼。
1 var person1 = { 2 name:‘小明‘, 3 age:24, 4 showName:function(){ 5 console.log(‘我的名字是:‘+this.name) 6 } 7 }; 8 var person1 = { 9 name:‘小華‘, 10 age:25, 11 showName:function(){ 12 console.log(‘我的名字是:‘+this.name) 13 } 14 };
- 工廠模式
- 工廠模式其實就是把需要一個個的編寫的對象,放在一個函數中統一的進行創建,說白了就是普通函數的封裝。
- 工廠模式總共3步驟:
1)引進原材料 --- 創建一個空對象
2)加工原材料 --- 加工對象:給對象添加屬性和方法;
3)輸出產品 --- 返回對象:return 對象;
1 function CreatePerson(name,age){ 2 var obj={};//1.創建一個空對象 3 //2.加工對象 4 obj.name=name; 5 obj.age=age; 6 obj.showName=function(){ 7 console.log(‘我的名字是:‘+this.name) 8 }; 9 return obj;//3.輸出對象; 10 } 11 var person1 = CreatePerson(‘小明‘,23) 12 var person2 = CreatePerson(‘小華‘,23) 13 person1.showName(); //我的名字是:小明 14 person2.showName(); //我的名字是:小華
- 工廠模式的優缺點
- 既然叫工廠模式,它就和我們周圍的工廠一樣,我們只需要把原材料放進去,就能得到我們需要的產品了。
- 工廠模式也解決了單例模式的批量生產的問題,避免了單例模式中的大量冗余代碼,進行系統的封裝,提高代碼的重復利用率
- 不過工廠模式跟我們js內置類的調用方法不同
- 構造函數模式
- 可以創建一個自定義的類,並且可以new出實例
- 構造函數做的就是類和實例打交道。
1 //構造函數:首字母大寫(約定俗成); 2 function CreatePerson(name,age){ //創建一個自定義的類 3 //構造函數中的this,都是new出來的實例 4 //構造函數中存放的都是私有的屬性和方法; 5 this.name=name; 6 this.age=age; 7 this.showName=function(){ 8 console.log(‘我的名字是:‘+this.name) 9 } 10 } 11 //實例1 12 var person1 = new CreatePerson(‘小明‘,25) 13 //實例2 14 var person2 = new CreatePerson(‘小華‘,24)
這裏說一下工廠模式和構造函數模式的區別:
1. 在調用的時候不同: 工廠模式:調用的時候,只是普通函數的調用createPerson(); 構造函數模式:new CreatePerson();
2. 在函數體內不同: 工廠模式有三步:
1)創建對象
2)加工對象
3)返回對象;
構造函數模式只有1步: 只有加工對象; 因為系統默認會為其創建對象和返回對象;
3. 構造函數默認給我們返回了一個對象,如果我們非要自己手動返回的話:
(1)手動返回的是字符串類型:對以前實例上的屬性和方法沒有影響;
(2)手動返回的是引用數據類型:以前實例身上的屬性和方法就被覆蓋了;實例無法調用屬性和方法;
構造函數的方法都是私有方法,每個實例調用的都是自己私有的方法,同樣也會有許多重復的代碼。
我們可以使用原型模式來解決每個實例中都有相同方法的函數的問題
- 原型模式
1 function CreatePerson(name,age){ 2 this.name=name; 3 this.age=age; 4 } 5 // 我們把公有的方法放到函數的原型鏈上 6 CreatePerson.prototype.showName = function(){ 7 console.log(‘我的名字是:‘+this.name) 8 } 9 var person1 = new CreatePerson(‘小明‘,25) 10 var person2 = new CreatePerson(‘小華‘,24) 11 person1.showName() //小明
###### 原型模式的關鍵:
1)每個函數數據類型(普通函數,類)上,都有一個屬性,叫prototype。
2)prototype這個對象上,天生自帶一個屬性,叫constructor:指向當前這個類;
3)每個對象數據類型(普通對象,prototype,實例)上都有一個屬性, 叫做__proto__:指向當前實例所屬類的原型;
這3句話理解了,下邊的東西就可以不用看了 //手動滑稽
通過例子我們來看這幾句話是什麽意思
1 function CreatePerson(name,age){ 2 this.name=name; 3 this.age=age 4 } 5 CreatePerson.prototype.showName=function(){ 6 console.log(‘我的名字是:‘+this.name) 7 } 8 var person1 = new CreatePerson(‘小明‘,25); 9 console.dir(person1)
在chrome瀏覽器控制臺中顯示
1 從圖中可以看出,person1這個對象上有name和age兩個屬性, 2 person1的__proto__指向了它的構造函數(CreatePerson)的prototype上, 3 而且還有一個showName的方法。 4 並且它們中有一條鏈關聯著: person1.__proto__ === CreatePerson.prototype
接著來看
1 function Foo(){ 2 this.a=1; 3 } 4 Foo.prototype.a=2; 5 Foo.prototype.b=3; 6 var f1 = new Foo; //沒有參數的話括號可以省略 7 console.log(f1.a) //1 8 console.log(f1.b) // 3 9 10 以這個為例, 11 當我們查找f1.a時,因為f1中有這個屬性,所以我們得出 f1.a=1; 12 當我們查找f1.b時,f1中沒有這個屬性,我們便順著f1.__proto__這條鏈去 13 它的構造器的prototype上找,所以我們得出了 f1.b = 3;
接著來說,Foo.prototype是個對象,那麽它的__proto__指向哪裏呢
還記的剛剛說的那句
每個對象數據類型(普通對象,prototype,實例)上都有一個屬性,叫做__proto__:指向當前實例所屬類的原型
此外,我們應該知道
每一個對象都是function Object這個構造函數的實例
所以我們可以接著還原這個原型圖
等等,圖上貌似多了個個Object.prototype.__proto__ 指向了null,這是什麽鬼?
我們這麽來理解,Object.prototype是個對象,
那麽它的__proto__指向了它的構造函數的prototype上,
最後發現了還是指向它自身,這樣轉了個圈貌似是無意義的,於是便指向了null
還沒完,我們發現對象都是函數(構造器)創造出來的,那麽函數是誰創造的呢?石頭裏蹦出來的麽?
在js中,function都是由function Function這個構造器創造的,每一個函數都是Function的實例
現在基本上我們就能得出了完整的原型圖了
是不是有點亂?根據我們剛剛講的是能把這個圖理順的,
這裏需要註意下,Function.__proto__是指向它的prototype的
多說一點,判斷數據類型的方法時,我們知道有個instanceof的方法
比如
A instanceof B
instanceof判斷的規則就是:
沿著A的__proto__這條線查找的同時沿著B的prototype這條線來找,如果兩條線能找到同一個引用(對象),那麽就返回true。如果找到終點還未重合,則返回false。
再來看我們之前的那個例子
1 function Foo(){ 2 this.a=1; 3 } 4 Foo.prototype.a=2; 5 Foo.prototype.b=3; 6 var f1 = new Foo; //沒有參數的話括號可以省略 7 console.log(f1.a) //1 8 console.log(f1.b) // 3 9 10 當我們查找f1.a時,因為f1中有這個屬性,所以我們得出 f1.a=1; 11 當我們查找f1.b時,f1中沒有這個屬性,我們便順著f1.__proto__這條鏈去它的構造器的prototype上找,所以我們得出了 f1.b = 3;
當我們查找一個對象的屬性時,先在這個對象的私有空間內查找,如果沒找到,就順著對象的__proto__這條鏈去它的構造器的ptototype上查找,如果還沒找到,接著沿__proto__向上查找,直到找到Object.prototype還沒有的話,這個值就為undefined,這就是所謂的原型鏈
列舉下網頁中的一些相關的原型鏈
有興趣的同學可自行通過瀏覽器控制臺看看我們常用的方法都是在哪個類上定義的,比如getElementsByTagName,addEventListener等等
繼承
在這裏就主要說一下組合繼承(call + 原型鏈)
1 function Father(){ 2 this.xxx= 80; 3 this.yyy= 100; 4 this.drink = function(){} 5 } 6 Father.prototype.zzz= function(){} 7 var father = new Father; 8 function Son(){ 9 this.aaa = 120; 10 this.singing = function(){} 11 Father.call(this); 12 } 13 Son.prototype = new Father; 14 Son.prototype.constructor = Son; 15 var son = new Son 16 console.dir(son)
這麽寫有個不好的地方就是:子類私有的屬性中有父類私有的屬性,子類公有的屬性中也有父類私有的屬性;
根據我們前邊的知識,我們可以這麽來改寫
1 function Father(){ 2 this.xxx= 80; 3 this.yyy= 100; 4 this.drink = function(){} 5 } 6 Father.prototype.zzz= function(){} 7 var father = new Father; 8 function Son(){ 9 this.aaa = 120; 10 this.singing = function(){} 11 Father.call(this); //利用call繼承了父類的私有屬性 12 } 13 Son.prototype.__proto__ = Father.prototype 14 var son = new Son 15 console.dir(son)
最後來一張思維導圖
第158天:面向對象入門