五、面向對象
- 什麼是面向對象:
程序中先用對象結構保存現實中一個事物的屬性和功能,然後再按需使用事物的屬性和功能,這種編程方法,就叫做面向對象編程 - 爲什麼要面向對象:因爲便於大量數據的管理和維護
- 何時:大部分的項目開發都用到面向對象的思想
- 如何:3步/3大特點:封裝、繼承、多態
1. 封裝
- 什麼是:創建一個對象,集中保存現實中一個事物的屬性和功能
- 爲什麼:便於大量數據的管理和維護
- 何時:只要使用面向對象的編程方式,都要先創建所需的所有對象,備用
- 如何使用封裝:3種方法
- 用{}創建一個對象(回顧)
-
格式:
var 對象名={ 屬性名:屬性值, ... : ...., 方法名:function(){ ... } ... ... }
-
如何訪問對象中的成員:(屬性和方法統稱爲對象的成員)
- 訪問對象中的一個屬性:
對象.屬性名
- 調用對象中的一個方法:
對象.方法名()
- 訪問對象中的一個屬性:
-
this
-
常出現的問題:對象自己的方法中,想使用對象自己身上的屬性,直接用會報錯,提示屬性名未定義
-
原因:任何情況下,不加任何前綴的普通變量,默認只能在函數作用域對象和全局作用域對象window中查找變量使用。無權擅自闖入一個對象中讀取對象的屬性
-
不好的解決方案:在變量前寫死當前對象的對象名:
lilei.sname
,lilei.sage
-
出現的問題:對象名僅僅是個普通的變量名,很可能隨時發生變化。如果方法中寫死舊對象名,則對象名已發生改變,方法中的執行立即出現錯誤,提示找不到舊對象名。被迫也要修改方法中寫死的對象名-----緊耦合----聯繫過於緊密,不靈活
-
好的解決辦法:this關鍵字
- 什麼是this:
每個函數中自帶----不用創建,可直接使用
自動指向正在調用函數的.
前的對象 - 何時用:只要在對象的方法中,想用當前對象中的屬性時,就需要用
this.屬性名
- 優點:鬆耦合。即使對象名發生變化,也不用修改方法中的this。因爲this會在調用方法時,自動獲得
.
前,正在調用的對象名
- 什麼是this:
-
-
示例:創建對象,保存一個對象的屬性和方法
<script> // 創建一個對象保存一個學生的屬性和功能 // 學生姓名:Li Lei // 年齡:11 // 會做自我介紹(intr):I'm Li Lei,I'm 11 var lilei={ // 對象的大括號不是作用域。僅僅是new Object()簡寫而已 sname:"Li Lei", sage:11, intr:function(){ console.log(`I'm ${this.sname},I'm ${this.sage}`); } } // 輸出lilei的年齡 console.log(`年齡:${lilei.sage}`); // 請lilei自我介紹 lilei.intr(); // 過了一年,lilei漲了一歲 lilei.sage++; // 再輸出lilei的年齡 console.log(`年齡:${lilei.sage}`); // 再請lilei自我介紹 lilei.intr(); // 輸出lilei現在在內存中的存儲結構 console.log(lilei); </script>
輸出結果:
年齡:11
I’m Li Lei,I’m 11
年齡:12
I’m Li Lei,I’m 12
{sname: “Li Lei”, sage: 12, intr: ƒ}
-
- 用new創建
- 創建過程—2步
- 先用new創建一個空對象:
var 對象名=new Object();
瞭解:非主流寫法:new和()可以省略,但至少保留一個 - 在強行給新的空對象賦值新屬性和新方法
對象名.新屬性名=屬性值;
對象名.新方法名=function(){...}
- 先用new創建一個空對象:
- 揭示了js中對象底層最本質的祕密:其實js中對象底層也都是關聯數組----列出兩者對比
- 存儲結構完全相同:都是名值對兒的集合
- 都可以用2種方式訪問成員:
- 標準:
數組名["下標名"]
對象名["屬性名"]
- 簡寫:
數組名.下標名
對象名.屬性名
- 標準:
- 都可隨時向不存在的下標位置,強行添加屬性,而不會報錯
- 都可訪問不存在的下標位置,不會報錯,而是返回
undefined
- 都可用for in遍歷其中每個成員
- 總結:對象底層其實就是關聯數組,對象只不過是管理數組的一種簡化用法而已
- 示例:筆試題:克隆一個對象
<script> // 筆試題:需求:克隆一個對象 var lilei={ sname:"Li Lei", sage:11 } // 錯誤做法:只是把lilei的對象地址給lilei2,不是克隆 // 一旦lilei2修改對象中的屬性,lilei也發生改變 // var lilei2=lilei; // console.log(lilei2); // 正確做法 function clone(oldObj){ // 1.創建空對象 var newObj={};//創建新對象地址,肯定和oldObj不同 // 2.遍歷舊對象中每個屬性 for(var key in oldObj){ // for in會一次遍歷oldObj中每個屬性名,並將屬性名一次賦值給in前的變量key // 3.給新對象強行賦值和舊對象同名的屬性,且屬性值也要相同 // newObj.key=oldObj.key;//錯誤:.key自動翻譯爲["key"] // newObj["key"]=oldObj["key"];//錯誤:因爲key是個變量,每循環一次,key的值可能發生變化 newObj[key]=oldObj[key];//正確 } return newObj; } var lilei2=clone(lilei); console.log(lilei); console.log(lilei2); console.log(lilei==lilei2);//false lilei.money=100; console.log(`lilei2的money:${lilei2.money}`);//undefined </script>
- 創建過程—2步
- 用構造函數反覆創建多個相同結構的對象
-
問題:前兩種創建對象的方式,一次只能創建一個對象。如果需要反覆創建多個相同結構的對象時,代碼就會很繁瑣。
-
何時:今後只要反覆創建多個相同結構的對象時,都用構造函數來創建對象
-
如何做:2步
- 定義一個構造函數,描述多個相同結構的對象的統一結構
function 類型名(形參變量列表){ //通常類型名首字母習慣大寫 this.屬性名=形參; ... ... this.方法名=function(){...} ... ... }
- 用new調用構造函數,反覆創建多個相同結構的對象
- 格式:
var 新對象=new 構造函數名(屬性值列表)
- new做了4件事—重點
- 第一步:new先創建了一個新的空對象
- 第二步:“認爹”----自動設置新對象的
__proto__
指向構造函數的原型對象-----繼承 - 第三步:調用構造函數,new自動將構造函數中this指向正在創建的這個新對象
- 所以構造函數中的this都指向要創建的新對象
- 構造函數中每個
this.屬性名=形參
,都在給新對象通過強行賦值新屬性的方式,添加新屬性 - 所以,構造函數中所有要加入到新對象中的屬性前都要加
this.
- 構造函數中所有加
this.屬性名
的屬性,最終都會添加到新對象中
- 第四步:返回新創建的對象地址保存到=左邊的對象名變量中
- 格式:
- 定義一個構造函數,描述多個相同結構的對象的統一結構
-
示例:定義學生類型的構造函數,反覆創建兩個學生對象
<script> // 想定義一個構造函數,來描述所有學生類型的對象的統一結構 // 需求:所有學生都要有學生姓名,學生年齡兩個屬性 // 所有學生都應該會做自我介紹 function Student(sname,sage){ this.sname=sname; this.sage=sage; // 方法都儘量不寫在構造函數中,寫在原型對象中---節約內存 this.intr=function(){ console.log(`I'm ${this.sname},I'm ${this.sage}`); } } // 創建兩個學生對象 var lilei=new Student("Li Lei",11); var hmm=new Student("Han Meimei",12); console.log(lilei); console.log(hmm); </script>
-
- 用{}創建一個對象(回顧)
2. 繼承
-
問題:構造函數的優點雖然是可重用結構代碼,便於維護,但是構造函數卻無法節約內存,反而浪費內存。因爲,放在構造函數中的方法,會爲每個子對象都反覆創建一個函數對象的副本。
所以:在構造函數中就不應該包含方法定義
解決辦法:用繼承來解決 -
什麼是繼承:父對象中的成員,子對象無需重複創建,就可直接使用,像使用自己的成員一樣使用
-
何時:只要多個子對象,希望共用同一個方法或屬性,就可用繼承的方式來定義一次,反覆使用----節約內存
-
如何:js中的繼承都是藉助於原型對選哪個實現的
-
原型對象:
- 其實在創建每個構造函數時,都會自動附贈一個空對象,名爲原型對象(prototype)
- 通過構造函數
.prototype
屬性,可獲得這個構造函數對應的一個原型對象
例如:想獲得Student類型的原型對象:Student.prototype
- 當構造函數創建對象時,new的第二步自動爲新對象添加"_proto_“屬性,將”_proto_"屬性指向當前構造函數的原型對象
例如:如果var lilei=new Student("Li Lei",11)
則new會自動:lilei.__proto__=Student.prototype;
- 結果
- 凡是這個構造函數創建出的新對象,都是原型對象的孩子(子對象)
- 放在原型對象中的屬性或方法,所有子對象無需重複創建,就可直接使用
-
如何爲空的原型對象添加共有的屬性或方法:強行賦值
構造函數.prototype.方法名=function(){ ... ... }
-
原型對象中的方法如何使用:子對象可以直接調用,像調用自己的方法一樣
子對象.原型對象中的方法名()
- 原理:js引擎會先在當前子對象中查找是否包含該方法
- 如果找到,就優先使用當前子對象自己的方法
- 如果沒找到,就會自動沿着"_proto_"去父對象(原型對象)中查找方法使用
-
總結:
- 構造函數中應該只包含屬性結構的定義
- 所有的方法,都應該強行添加到原型對象中,所有子對象共用一份----節約內存
-
示例:爲Student的原型對象中添加共有方法
<script> function Student(sname,sage){ this.sname=sname; this.sage=sage; // 構造函數中不再包含任何方法的定義 } console.log(Student.prototype); // 爲原型對象添加方法:希望所有的學生可以叢自我介紹intr Student.prototype.intr=function(){ console.log(`I'm ${this.sname},I'm ${this.sage}`); } // 通過構造函數創建2個對象 var lilei=new Student("Li Lei",11); var hmm=new Student("Han Meimei",12); lilei.intr(); hmm.intr(); console.log(lilei); console.log(hmm); // 驗證lilei的父對象是構造函數的原型對象 console.log(lilei.__proto__==Student.prototype); </script>
輸出結果:
{constructor: ƒ}intr: ƒ ()constructor: ƒ Student(sname,sage)_proto_: Object
I’m Li Lei,I’m 11
I’m Han Meimei,I’m 12
Student {sname: “Li Lei”, sage: 11}
Student {sname: “Han Meimei”, sage: 12}
true -
“原型對象"就是"父對象”,"父對象"就是"原型對象"都是同一個東西,只是稱呼不同
3. 多態
總結: 面向對象: 三大特點: 封裝 繼承 多態
- 封裝: 創建對象 3種方法:
- 只創建一個對象,且已經知道對象的成員是什麼:
var 對象名={ 屬性名: 屬性值, ... : ... , 方法名: function(){ ... this.屬性名 ... } }
- 只創建一個對象,但是暫時不知道對象的成員: 2步
- 先創建一個空對象等着:
var 對象名={} //new Object()的簡寫
- 等知道對象的成員之後:
對象.屬性名=屬性值
對象.方法=function(){ ... this.屬性名 ...}
- 先創建一個空對象等着:
- 想反覆創建多個相同結構的對象時: 2步:
- 先定義構造函數:
//因爲構造函數是爲了描述同一類型的多個對象統一的屬性結構 //所以,構造函數名通常是一種類型的名稱,比如學生,商品,訂單,用戶... function 類型名(形參變量列表){ this.屬性名=形參變量; ... : ... , //構造函數不要包含方法的定義 }
- 用new調用構造函數創建對象
var 對象名=new 類型名(屬性值列表)
new 做了4件事:- 創建一個新的空對象
- 讓子對象繼承構造函數的原型對象(自動設置子對象的_ proto _指向構造函數的prototype對象)
- 調用構造函數,同時將構造函數中的this臨時替換爲new正在創建的這個新對象
結果構造函數中的每一句話,都會強行給新對象添加構造函數早就規定好的統一的新屬性 - 返回新對象地址,保存到等號左邊的變量中
- 先定義構造函數:
- 只創建一個對象,且已經知道對象的成員是什麼:
- 繼承:
- (自動)每定義一個構造函數,都會自動附贈一個共的原型對象(prototype)
- (自動)當用new創建子對象時,第二步new會自動設置子對象的_ proto _指向構造函數的原型對象(prototype)。結果,子對象繼承父對象
- (自動)從此,父對象中的成員,子對象無需重複創建,就可直接使用
- (需要我們做的) 向原型對象中爲所有子對象添加共有的方法
構造函數.prototype.共有方法=function(){ ... this.屬性名 ... }
- 多態:
重寫: 如果從父對象繼承來的個別成員不好用!就可在子對象中定義同名成員。結果,子對象再使用同名成員時,優先使用自己自有的同名成員。不再使用父對象不好用的同名成員。