1.基礎
類,是一種抽象的,并不實際存在的,表示一種事物共有特征的描述
物件,是具體的,實際存在的,類中的某一個體
JavaScript是一個典型的“面向程序思想”的語言,語言中不存在類和物件的概念,
但是,由于JavaScript經常要解決“面向物件思想”的問題,所以JavaScript使用一些方法模擬面向物件思想的特征,
2.類的創建
建構式,通過 new命令生成物件的函式稱為建構式,建構式一般首字母大寫
new命令的作用是先創建一個物件,然后讓物件呼叫建構式,因此,建構式中的 this指向的是 new創建的物件
語法示例:
function Student(name, age, sex){
this .name=name;
this .age=age;
this .sex=sex;
};
var Lili = new Student('Lili', 20, 'female');
上述代碼中,函式 Student代表類名,通過傳遞實參向類的屬性賦值,new命令將這些鍵值對構造為物件,然后賦值給變數
注意,這種方法只是 js中眾多創建類的方式之一,且并不是最優的創建方式
3.面向物件思想
面向物件(oop),創建一個物件,讓物件擁有做某件事的能力(給物件屬性和方法),然后命令物件做某件事(封裝、繼承、多型)
面向程序(pop),分析出解決問題的所有步驟,將其構建為一個一個函式,然后將這些步驟按照一定順序實作(順序、選擇、回圈)
oop的核心就是如何構建一個物件,也就是“物件的封裝”!
4.封裝
是指構造具有某種特征的類,以通過對其進行實體化,來獲得滿足需求的物件的程序
封裝的特征:
公有,物件中的屬性和方法,在物件外部能夠直接訪問的,稱為公有屬性和方法
私有,物件中的屬性和方法,僅在物件內部才可以使用的,稱為私有屬性和方法
在建構式中,通過“this .屬性”的方式為類添加公有的屬性和方法
this.property所添加的內容在物件外部能夠直接被訪問
在建構式中,添加區域變數和閉包的方式為類添加私有的屬性和方法
區域變數保證了物件外部無法直接獲取,閉包保證了物件外部可以間接獲取
對于所有物件都具有的共同屬性,為了減少在構造物件時重復傳遞相同屬性值,通過 prototype屬性進行統一定義
語法:類名 .prototype .相同屬性值的屬性 = 屬性值;
5.原型
js中給函式提供了一個物件型別的屬性,叫作 prototype(原型)
原型歸函式所有,不用創建,是默認就存在的
語法:Class .prototype .property = ' value ';
注意,js中提供了一種機制,如果是通過類創建的物件,當訪問的屬性在物件中沒有找到時,
會去找創建這個物件的類的原型屬性,如果能找到,則視為當前物件擁有這個屬性,
本質上,原型的存在就是為了給類的物件添加共同的屬性
作用,使用原型能夠有效地節約記憶體空間,通過原型創建的屬性和方法,能夠被所有這個類創建的物件訪問
6.建構式語法小結
function ClassName(para1,para2,...) { //類名首字母須大寫
var privateProperty = 'value'; //定義私有屬性
var privateFunc = function () { } //定義私有方法
this.publicProperty1 = para1; //定義公有屬性
this.publicProperty2 = para2;
this.publicFunc = function(){ }; //定義公有方法(特權函式,可以讀寫privateProperty)
... ...
}
ClassName.prototype.publicProperty3 = 'value3'; //定義原型(共有)屬性及屬性值
ClassName.prototype.publicFunc=function () { }; //定義原型(共有)方法
var obj = new ClassName(實參1,實參2,…); //通過類創建物件
7.原型屬性
結構:原型是一個物件,在原型中通常有兩個屬性
① 構造器Constructor,該屬性指向了這個類本身(表明當前原型歸屬于哪個類所有)
② 原型指向_proto_,該屬性指向原型本身,提供給通過類創建的物件使用
作用:創建類的公有屬性和公有方法,為創建物件服務
節約記憶體空間,不必為每一個物件都分配公有屬性和公有方法的記憶體
缺點:原型中不能保存陣列這類參考型別的資料
因為地址傳遞的問題會導致出現修改的連鎖變化
比如,通過 obj .publicProperty .pop(); 洗掉共有屬性陣列中的元素,
會導致其他物件訪問 prototype .publicProperty 陣列時也缺少了這個元素
8.原型鏈
原型鏈構成,由物件的“_proto_”屬性和物件的建構式的原型的“_proto_”屬性構成的鏈式結構稱為原型鏈
原型鏈的頂端是 Object物件,Object物件沒有“_proto_”屬性,或者說它的“_proto_”屬性指向了自身
原型鏈作用,訪問物件的屬性或方法時,首先在物件本身中查找是否擁有這個屬性或方法,
如果沒有找到,那么就沿著原型鏈逐級向上查找,直到 Object為止
在任何一級尋找到這個屬性或方法都視為物件擁有這個屬性或方法(繼承)
原型鏈創建,函式的原型(prototype) 設定為 另一個函式的物件(實體)
語法示例:ClassName101 .prototype = new ClassName1 ;
9.繼承
在面向物件的語言中,子類能夠在不宣告的情況下,使用父類的屬性和方法的特性叫作繼承
而JavaScript語言本質上并不是一門面向物件的語言,所以,需要通過某種手段來模擬繼承,這個方法就是“原型鏈”
鏈式繼承示例:
<script>
function RichMan() {}
RichMan.prototype.money='billions of pounds';
var father=new RichMan(); //創建父類的實體化物件
function Son() {}
Son.prototype=father; //創建子類繼承關系
var boy=new Son(); //創建子類實體化物件
console.log(boy.money); //billions of pounds
</script>
存在的問題:
① 原型鏈繼承,子類實體化不能向父類建構式傳參,但是可以直接訪問父類的原型屬性
構造繼承,只能訪問到父類實體屬性,實體化時可以向父類建構式傳參,但是不能訪問父類的原型屬性和方法
② 原型鏈繼承,子類 prototype的 constructor屬性,實際上就是父類 prototype的 constructor屬性,這樣并不合理
建構式,子類 prototype的 constructor屬性是子類本身,父類也是同樣,但構造繼承的子類無法享有父類的prototype屬性和方法
構造繼承示例:
function RichMan(fcash,fhouse,fcar) {
this.cash=fcash;
this.house=fhouse;
this.car=fcar;
}
/* RichMan.prototype.money='billions of pounds';
var father=new RichMan(); //創建父類的實體化物件*/
function Son(scash,shouse,scar) {
RichMan.call(this,scash,shouse,scar); //創建子類構造繼承關系
}
var john=new Son(1,2,3); //可以訪問父類實體屬性,but not 原型屬性
var dancy=new Son(4,5,6);
10.組合繼承
為了解決“原型鏈繼承”和“構造繼承”的各自缺點,在創建子類繼承時同時使用兩種繼承方式,即組合繼承
1 <script> 2 function RichMan(fcash,fhouse,fcar) { 3 this.cash=fcash; 4 this.house=fhouse; 5 this.car=fcar; 6 } 7 RichMan.prototype.money='billions of pounds'; 8 // var father=new RichMan(); //創建父類的實體化物件 9 10 function Son(scash,shouse,scar) { 11 RichMan.call(this,scash,shouse,scar); //創建“構造繼承”關系 12 } 13 Son.prototype=new RichMan(); //創建“鏈式繼承”關系 14 var john=new Son(1,2,3); 15 var dancy=new Son(4,5,6); 16 console.log(john); //Son {cash: 1, house: 2, car: 3} 17 console.log(dancy.money); //billions of pounds 18 19 /* Son.prototype=father; //創建子類繼承關系 20 var boy=new Son(); //創建子類實體化物件 21 console.log(boy.money); //billions of pounds*/ 22 </script>
組合繼承的弊端:
在子類中呼叫了兩次父類建構式,一次用于構造繼承,一次用于鏈式繼承,也就是對實體屬性初始化了兩次,
但這一弊端不太致命,子類在實體化時,構造繼承的實體屬性覆寫了鏈式繼承的實體屬性,只是多消耗了一些記憶體,
寄生組合繼承
核心思想:通過寄生方式,砍掉父類的實體屬性,這樣就能在呼叫兩次父類的構造的時候,不會再次實體屬性/方法,
1 <html lang="en"> 2 <head> 3 <meta charset="UTF-8"> 4 <title>繼承</title> 5 </head> 6 <body> 7 8 <script> 9 function RichMan(fcash,fhouse,fcar) { 10 this.cash=fcash; 11 this.house=fhouse; 12 this.car=fcar; 13 } 14 RichMan.prototype.money='billions of pounds'; 15 // var father=new RichMan(); //創建父類的實體化物件 16 17 function Son(scash,shouse,scar) { 18 RichMan.call(this,scash,shouse,scar); //創建子類“構造繼承”關系 19 } 20 // Son.prototype=new RichMan(); //創建“鏈式繼承”關系 21 Son.prototype.constructor=Son; //將子類原型屬性‘constructor’指向子類本身! 22 23 (function () { //創建自執行函式,并嵌套一個空的建構式 Medi, 24 function Medi() { } //將空建構式插入到原型鏈作為中間節點,即原子類變為孫類 25 Medi.prototype=new RichMan(); //這樣就避免在每一次子(孫)類實體化時進行兩次父類實體化屬性和方法 26 Son.prototype=new Medi(); //也就是用這種寄生方式替代原來的直接“鏈式繼承”關系 27 }()); 28 29 var john=new Son(1,2,3); 30 var dancy=new Son(4,5,6); 31 console.log(john); //Son {cash: 1, house: 2, car: 3} 32 console.log(dancy.money); //billions of pounds 33 34 /* Son.prototype=father; //創建子類繼承關系 35 var boy=new Son(); //創建子類實體化物件 36 console.log(boy.money); //billions of pounds*/ 37 </script> 38 </body> 39 </html>
11.設計模式
設計模式(Design Pattern)是一套被反復使用的、多數人知曉的、經過分類的代碼設計經驗的總結(模板)
作用:提高代碼可重用性、讓代碼更容易被他人理解、提高代碼的可靠性,
設計模式使代碼撰寫真正工程化,是軟體工程的基石脈絡,如同大廈的結構一樣
常見種類:
① 工廠模式
② 建構式模式
③ 原型模式
④ 混合模式
⑤ 動態原型模式
12.工廠模式
<script>
function RichMan(fcash,fhouse,fcar) {
var richMan={}; //定義區域變數,型別為Object物件
richMan.cash=fcash; //直接定義內部物件的屬性
richMan.house=fhouse;
richMan.car=fcar;
return richMan; //將內部物件作為呼叫函式的回傳值
}
var man=RichMan('muchCash','bigHouse','luxuryCar');
console.log(man);
console.log(man instanceof RichMan); //回傳值為 false
</script>
工廠模式是軟體開發中經常被使用的一種設計模式
instanceof 方法一個實體是否歸屬于一個類,
通過工廠模式創建的物件,最大的問題是無法確定其屬于哪一個類!
13.建構式模式
<script>
function RichMan(fcash,fhouse,fcar) {
this.cash=fcash; //定義this屬性
this.house=fhouse;
this.car=fcar;
}
//通過 new命令創建物件
var man=new RichMan('muchCash','bigHouse','luxuryCar');
console.log(man);
console.log(man instanceof RichMan); //回傳值為 true
</script>
建構式和工廠模式最大的區別是:
沒有顯示創建一個物件,而是通過 new命令隱式創建一個物件
然后讓隱式物件來實際執行建構式,因此建構式中的 this指向的是這個隱式物件
通過建構式創建的物件可以明確判斷其歸屬于哪一個類
建構式創建物件必須使用 new命令,函式名的首字母通常大寫
建構式模式最大的問題是面對子類物件共有的屬性值,不能有效地節約記憶體占用!
14.原型模式
<script>
function RichMan(fcash,fhouse,fcar) {}
//使用 prototype方法定義類的共有屬性
RichMan.prototype.cash='muchCash';
RichMan.prototype.house='bigHouse';
RichMan.prototype.car='luxuryCar';
//通過 new命令創建物件
var man=new RichMan();
man.house='manyHouse'; //對于相同屬性不同屬性值時單獨賦值
console.log(man);
console.log(man instanceof RichMan); //回傳值為 true
</script>
弊端:在處理不同屬性值的公有屬性時,增加了記憶體的空間占用!
15.混合模式
<script>
function RichMan(fcash,fhouse,fcar) {
this.cash=fcash;
this.house=fhouse;
this.car=fcar;
}
RichMan.prototype.advantage=function () {
console.log('數錢數到手抽筋')
}
//通過 new命令創建物件
var man=new RichMan('muchCash','bigHouse','luxuryCar');
console.log(man);
man.advantage();
</script>
16.動態原型模式
<script>
function RichMan(fcash,fhouse,fcar) {
this.cash=fcash;
this.house=fhouse;
this.car=fcar;
//使用“懶加載”的方式,定義共有屬性的原型
if (typeof RichMan._initialized=='undefined'){
RichMan.prototype.advantage=function () {
console.log('數錢數到手抽筋')
}
RichMan._initialized=true;
}
}
//通過 new命令創建物件
var man=new RichMan('muchCash','bigHouse','luxuryCar');
console.log(man);
man.advantage();
</script>
懶加載:使用時才加載和占用記憶體空間,在沒有使用之前相當于不存在
動態原型模式和混合模式很相似,二者都是為了解決原型模式中所有內容都公有的問題
動態原型模式的特點在于,
通過判斷一個類的 “._initialized” 屬性的型別(typeof),進而判斷這個類有沒有被實體化過,
如果沒有被實體化過,在第一次呼叫(初始化)時就會將其釋放,并將屬性值寫為 true,
“._initialized”屬性是每一個類都擁有的私有屬性,它僅用來表示類是否被實體化過,是Boolean型別的可讀寫屬性,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/943.html
標籤:JavaScript
下一篇:蒲公英 · JELLY技術周刊 Vol.21 -- 技術周刊 · React Hooks vs Vue 3 + Composition API
