五、面向對象
1. 繼承
- 自有屬性和共有屬性
-
什麼是自有屬性:保存在子對象中,只歸子對象獨有的屬性
-
什麼是共有屬性:保存在原型對象中,歸當前類型下所有子對象共有的屬性
-
獲取屬性值時:無論獲取自有屬性值,還是獲得共有屬性值,都可用
子對象.屬性名
,無差別 -
修改屬性值時:
-
如果修改一個子對象的自有屬性,纔可以
子對象.自有屬性=新值
-
如果修改多個子對象共有的屬性,必須使用原型對象親自修改:
構造函數.prototype.共有屬性=新值
-
錯誤做法:如果強行用子對象直接修改共有屬性:結果,原型對象中的共有屬性保持不變,而是隻給當前這一個子對象添加一個新的同名的自有屬性。從此,這個子對象,因爲已經有了同名的自有屬性,就不會再使用同名的共有屬性。從此,共有屬性發生變化,當前子對象的這個同名自有屬性也不會跟隨變化。從此,這個子對象和其他子對象,在這個屬性的使用上,分道揚鑣。
-
-
示例:修改共有屬性
<script> function Student(sname,sage){ this.sname=sname; this.sage=sage; } // 希望給所有學生添加一個班級名屬性 Student.prototype.className="初一2班"; var lilei=new Student("Li Lei",11); var hmm=new Student("Han Meimei",12); console.log(lilei); console.log(hmm); console.log(lilei.className,hmm.className); // "初一2班" "初一2班" // 修改自有屬性 lilei.className="初二2班"; console.log(lilei.className,hmm.className); // "初二2班" "初一2班" // 過了一年,同學們升了一級 // 修改原型對象中共有屬性: Student.prototype.className="初二2班"; console.log(lilei.className,hmm.className); // "初二2班" "初二2班" // 又過了一年,同學們又升了一級 // 修改原型對象中共有屬性: Student.prototype.className="初三2班"; console.log(lilei.className,hmm.className); // "初二2班" "初三2班" // 此時lilei的className就跟原型對象中的className屬性分道揚鑣 </script>
-
- 內置類型的原型對象:
-
什麼是內置類型/對象:ES標準中已經規定的,瀏覽器已經定義好的,我們可以直接使用的類型/對象
-
11種內置類型對象
String Number Boolean----包裝類型(擴展)
Array Date RegExp Math(Math不是類型,而是一個對象,不能用new)
Error
Function Object
global(不是類型,是一個對象,不能new,且在瀏覽器中被window代替)
除了Math和global兩種外,其餘9中都可以new -
今後凡是可以new的,都是構造函數。只要有構造函數,都會牽扯出一個大家庭,每個大家庭中,至少包含2個:
- 構造函數(媽媽)
- 負責反覆創建多個相同結構的子對象
- 構造函數肚子裏的屬性,會成爲將來子對象中的自由屬性
- 原型對象(爸爸)
- 負責替該類型所有子對象集中保管共有的方法
- 原型對象有什麼方法,子對象也就有什麼方法----繼承
- 構造函數(媽媽)
-
比如:內置類型Array:包含2部分
function Array(){... 內部代碼 看不見 ...}
- 所有數組共用的函數,都放在Array.prototype中
- 如果想用的數組函數,原型對象中沒有,可以自己手動想數組的原型對象中添加一個新函數,結果,將來所有數組的子對象,都可用這個自定義的公共函數
- 示例:爲數組原型對象中添加求和函數sum
<script> // 需求:爲數組添加一個可對數組內容求和的函數 Array.prototype.sum=function(){ // this:指將來調用這個函數的某個數組的子對象 var result=0; for(var i=0;i<this.length;i++){ result+=this[i]; } return result; } console.log(Array.prototype); //[sum: ƒ, constructor: ƒ, concat: ƒ, copyWithin: ƒ, fill: ƒ, …] var arr1=[1,2,3]; console.log(arr1.sum());//6 var arr2=[1,2,3,4,5]; console.log(arr2.sum());//15 </script>
-
- 原型鏈
-
什麼是原型鏈:由多級父元素逐級繼承形成的鏈式結構
-
保存着一個對象可用的所有屬性和方法
-
控制着成員的使用順序:先自有,再共有
-
示例:驗證原型鏈
<script> function Student(sname,sage){ this.sname=sname; this.sage=sage; } Student.prototype.intr=function(){ console.log(`I'm ${this.sname},I'm ${this.sage}`) } var lilei=new Student("Li Lei",11); console.log(lilei);//Student {sname: "Li Lei", sage: 11} console.log(lilei.__proto__==Student.prototype);//true console.log(lilei.__proto__.__proto__==Object.prototype);//true console.log(Student.__proto__==Function.prototype);//true console.log(Student.__proto__.__proto__==Object.prototype);//true </script>
-
2. 多態
-
什麼是多態:同一個函數,在不同情況下,表現出不同的狀態
-
包括2種情況:重載和重寫(override)
-
什麼是重寫:子對象中定義了和父對象中重名的成員
-
爲什麼重寫:因爲從父對象中繼承來的屬性或方法並不總是好用的
-
何時重寫:只要從父對象繼承來的某個成員不好用,就可以重寫
-
如何:只要在子對象中,定義和父對象中名稱相同的一個成員,從此子對象再使用這個成員時,都會優先使用自己定義的成員,而不再使用父對象的成員
-
示例:在自定義類型和對象中重寫Object原型對象裏的toString()方法
<script> function Student(sname,sage){ this.sname=sname; this.sage=sage; } // 希望所有學生類型的對象,都有toString()可用,可輸出學生的屬性值 // 在Student的原型對象中重寫和父對象中同名的toString()方法 Student.prototype.toString=function(){ return `{ sname:${this.sname},sage:${this.sage} }` } var lilei=new Student("Li Lei",11); var obj={ x:1, y:2, // 希望obj也有好用的toString() toString:function(){ return `{x:${this.x},y:${this.y}}` } } var arr=[1,2,3]; var now=new Date(); console.log(lilei.toString());//{ sname:Li Lei,sage:11 } console.log(lilei);//Student {sname: "Li Lei", sage: 11} console.log(obj.toString());//{x:1,y:2} console.log(arr.toString());//1,2,3 console.log(now.toString());//Tue May 05 2020 20:38:23 GMT+0800 (中國標準時間) </script>
3. 自定義繼承
如果子對象覺得整個父對象都不好用,可以換父對象:2種方法
- 只修改一個對象的父對象
-
不推薦寫法:
子對象.__proto__=新父對象
問題:不是所有瀏覽器都開放__proto__
讓我們隨意使用 -
推薦的等效做法:
Object.setPrototypeOf(子對象,父對象)
-
示例:僅修改hmm的父對象
<script> function Student(sname,sage){ this.sname=sname; this.sage=sage; } Student.prototype.bal=9.9; Student.prototype.car="none"; var lilei=new Student("Li Lei",11); var hmm=new Student("Han Meimei",12); var father={ bal:100000000000000, car:"infiniti" } // hmm想用father中的成員,可讓hmm繼承father // hmm.__proto__=father; Object.setPrototypeOf(hmm,father); console.log(hmm.bal,hmm.car);//100000000000000 "infiniti" console.log(lilei.bal,lilei.car);//9.9 "none" </script>
-
- 同時修改多個子對象的父對象
-
只要更換構造函數的prototype屬性,指向新的原型對象即可
構造函數.prototype=新原型對象;
-
時機:應該在創建子對象之前就要更換
好處:之後再創建的子對象都自動繼承新的父對象
而已經創建的子對象所繼承的父對象不變,仍是舊父對象
-
示例:同時更換lilei和hmm的父對象
<script> var father={ bal:100000000, car:"infiniti" } function Student(sname,sage){ this.sname=sname; this.sage=sage; } Student.prototype.bal=9.9; Student.prototype.car="none"; // 創建更換原型對象之前的子對象 var xiaoming=new Student("Xiao Ming",13); // 將構造函數的原型對象更換 Student.prototype=father; // 新原型對象的構造函數更換爲Student father.constructor=Student; // 創建子對象 var lilei=new Student("Li Lei",11); var hmm=new Student("Han Meimei",12); console.log(xiaoming.bal,xiaoming.car);//9.9 "none" console.log(lilei.bal,lilei.car);//100000000 "infiniti" console.log(hmm.bal,hmm.car);//100000000 "infiniti" </script>
-
六、ES5
ECMAScript語言(js語言的核心語法)標準的第五個升級版本.
1. 嚴格模式
- 什麼是嚴格模式:比普通js運行機制要求更嚴格的模式
- 爲什麼:舊的js語言存在很多廣受詬病的缺陷
- 何時:所有的js程序,都需要運行在嚴格模式下
- 如何開啓嚴格模式:在當前代碼段的頂部添加字符串:
"use strict"
- 新要求:4個
- 禁止給未聲明的變量賦值
- 舊js中:強行給未聲明的變量賦值,會自動在全局創建該變量。-----極容易造成全局污染
- 示例:舊js中,非嚴格模式下給未聲明的變量賦值
<script> // "use strict"//啓用嚴格模式 function send(){ var gf; // 假設不小心把gf寫成了qgf qgf="今晚308,w84u"; console.log(gf); } send(); console.log(qgf); // 如果未啓用嚴格模式 // 輸出undefined和今晚308,w84u // 如果啓用嚴格模式 // 報錯:qgf未定義 </script>
- 靜默失敗升級爲錯誤
- 靜默失敗:執行不成功,但是還不報錯
- 缺點:靜默失敗極其不便於調試
- 嚴格模式下:將所有的靜默失敗都升級爲錯誤
- 優點:極其便於調試,避免歧義
- 示例:對比嚴格、非嚴格模式下執行錯誤的操作
<script> "use strict"//啓用嚴格模式 var eric={ eid:1001,//只讀 ename:"埃裏克" } // 設置eid爲只讀 Object.defineProperty(eric,"eid",{ writable:false,//設置eric的eid屬性爲不可修改--只讀 configurable:false//設置不允許再修改writable }) // 嘗試修改eric的eid eric.eid=1002; console.log(eric); // 不啓用嚴格模式 // 不報錯 輸出{eid: 1001, ename: "埃裏克"} // 啓用嚴格模式 // 報錯:提示不可修改只讀屬性eid </script>
- 普通函數調用和匿名函數自調中的this默認值undefined,而不再指window
- 舊js:普通函數調用和匿名函數自調中的this默認指window
- 問題:容易導致全局污染
- 嚴格模式下:普通函數調用和匿名函數自調中的this指undefined,而不再指window
- 好處:大大減少了因爲this導致的全局污染
- 示例:對比嚴格、非嚴格模式下的錯誤使用構造函數
<script> "use strict"//啓用嚴格模式 function Student(sname,sage){ this.sname=sname; this.sage=sage; } // 正確使用構造函數的用法 // var lilei=new Student("Li Lei",11); // console.log(lilei); // 錯誤的使用構造函數的用法 var hmm=Student("Han Meimei",12); // Student前沒有.也沒有new,所以Student中的this暫時指向window // 相當於:window.sname="Han Meimei"; // 相當於:window.sage=12; console.log(hmm); console.log(window.sname); console.log(window.sage); // 非嚴格模式下輸出: // undefined // Han Meimei // 12 // 嚴格模式下輸出: // 報錯:無法設置Student中未定義的屬性sname </script>
- 禁止使用
arguments.callee
- 什麼是
arguments.callee
:是在函數內部,獲得當前函數本身的一種關鍵詞 - 何時:遞歸
- 問題:如果在函數內遞歸調用時,寫死當前函數的函數名,則一旦當前函數名改變,就必須同時修改函數體中寫死的函數名,一旦漏寫就報錯----緊耦合
- 解決:在函數內用
arguments.callee
自動獲得當前函數對象本身,直接用當前函數對象進行遞歸調用,與函數名無關 - 爲什麼嚴格模式要禁用
arguments.callee
:因爲遞歸調用效率極低(重複計算量太大) - 所以嚴格模式強力不建議使用遞歸調用
arguments.callee
,使用就會報錯 - 解決:多數遞歸調用都可以用循環來解決----但是難度較大
- 總結:但是改用遞歸還是首先選擇遞歸----因爲簡單
除非遞歸在項目中確實影響效率了,才被迫找循環的方法替代 - 示例:使用遞歸實現斐波那契數列
<script> "use strict"//啓用嚴格模式 // 斐波那契數列 // 1 1 2 3 5 8 13 21 34 55 // 1 2 3 4 5 6 7 8 9 10 // 數學公式:f(1)=f(2)=1,n>2是f(n)=f(n-1)+f(n-2) function f(n){ if(n<3){ return 1; }else{ var fun=arguments.callee;//自動獲得當前函數本身 return fun(n-1)+fun(n-2); } } // 測試 console.log(f(10)); // 非嚴格模式下輸出:55 // 嚴格模式下:報錯 </script>
- 什麼是
- 禁止給未聲明的變量賦值
總結:
一、面向對象
-
封裝: 創建對象 3種方法:
- 只創建一個對象,且已經知道對象的成員是什麼:
var 對象名={ 屬性名: 屬性值, ... : ... , 方法名: function(){ ... this.屬性名 ... } }
- 只創建一個對象,但是暫時不知道對象的成員: 2步
- 先創建一個空對象等着:
var 對象名={} //new Object()的簡寫
- 等知道對象的成員之後,再爲對象強行添加新屬性和方法:
對象.屬性名=屬性值
對象.方法=function(){ ... this.屬性名 ...}
- 先創建一個空對象等着:
- 想反覆創建多個相同結構的對象時: 2步:
- 先定義構造函數:
//因爲構造函數是爲了描述同一類型的多個對象統一的屬性結構 //所以,構造函數名通常是一種類型的名稱,比如學生,商品,訂單,用戶... function 類型名(形參變量列表){ this.屬性名=形參變量; ... = ... //今後構造函數中不要包含方法定義 }
- 用new調用構造函數創建對象
var 對象名=new 類型名(屬性值列表);
new 做了4件事:- 創建一個新的空對象
- 讓子對象繼承構造函數的原型對象(自動設置子對象的_ proto _指向構造函數的prototype對象)
- 調用構造函數時,同時將構造函數中的this臨時替換爲new正在創建的這個新對象
結果構造函數中的每一句話,都會強行給新對象添加構造函數早就規定好的統一的新屬性 - 返回新對象地址,保存到等號左邊的變量中
- 先定義構造函數:
- 只創建一個對象,且已經知道對象的成員是什麼:
-
繼承:
- 何時: 今後只要多個子對象共用的方法或屬性值,只要集中放在原型對象中一份,所有子對象即可共用。
- 向原型對象中爲所有子對象添加共有的方法
構造函數.prototype.共有方法名=function(){ ... this.屬性名 ... }
-
多態:
重寫: 如果從父對象繼承來的個別成員不好用!就可在子對象中定義同名成員。結果,子對象再使用同名成員時,優先使用自己自有的同名成員。不再使用父對象不好用的同名成員。 -
自定義繼承: 如果子對象覺得整個父對象都不好用,可認別的父對象當爹
- 只修改一個子對象的父對象:
- 不推薦:
子對象.__proto__=新父對象
- 推薦:
Object.setPrototypeOf(子對象, 新父對象)
- 不推薦:
- 如果更換該類型下所有子對象的父對象
構造函數.prototype=新父對象
- 時機: 最好在創建子對象之前,就要替換!
this的指向: 4種:
注意:判斷this一定不要看定義在哪兒!!只看調用時.前是誰。- obj.fun() fun中的this->.前的obj對象
- new Fun() Fun中的this-> new正在創建的新對象
- 構造函數.prototype.fun=function(){ … } fun中的this->將來調用這個fun()函數的當前類型的子對象
- (function(){ … })() 或 普通函數調用fun() this默認->window
- 只修改一個子對象的父對象:
二、ES5
- 嚴格模式: 4個新要求:
(1). 禁止給未聲明的變量賦值
(2). 靜默失敗升級爲錯誤
(3). 匿名函數自調和普通函數調用中的this不再指window,而是undefined
(4). 禁用了arguments.callee,不推薦使用遞歸,但該寫還是要寫。
補充: 函數也是一個對象,既保存函數體,同時也有自己的屬性
console.log(Student) 僅輸出函數內容
console.dir(Student) 僅輸出函數對象在內存中的屬性
以上兩個輸出的內容合起來,纔是一個完整的函數對象。
補充: 不要用for in
來遍歷索引數組
因爲in不僅遍歷當前對象的所有自有成員,而且會延_ _proto_ _
繼續遍歷原型對象中深紫色的共有成員(忽略淺紫色的成員)。