海闊憑魚躍,天高任鳥飛。Hey 你好!我是貓力Molly
一萬個人心中有一萬個哈姆雷特,一萬個開發者心中便有一萬種對物件導向思想的理解。這裡我只粗淺的闡述一下我對物件導向思想的理解,不喜輕噴,歡迎大家評論區留言討論。
基本概念
在程式裡,我們通過使用物件去構建現實世界的模型,把原本很難(或不可)能被使用的功能,簡單化並提供出來,以供訪問
這段解釋摘抄自MDN,讀起來甚是繞口。
這裡我們可以採用填鴨法來理解物件導向,大致是說看起來像只鴨子,那麼它就是一隻鴨子。人與機器不同的是,人類具備主觀意識而機器沒有,一個具備尖尖的嘴,扁扁的腦袋,嘎嘎嘎的叫聲並且還會游泳的生物,那麼這便是我們使用物件思想去構建了一隻鴨子的類。機器不會像人一樣主觀意識去設想這其實是一隻大鵝。
物件的組成
一個基本的物件由若干個資料型別組成,往大的類別上劃分的話,可以分為行為和屬性
屬性:所有的資料型別都可以認為是物件的屬性(小鴨子的體重,翅膀,腳丫子等等都是屬性)
行為:一般指函式,賦予物件能力(小鴨子會游泳,那麼游泳這個行為就是小鴨子的能力)
例項化物件
至此,我們已經建立了一隻鴨子類,這時候鴨子僅僅是一個初始化狀態,相當於被冰封。我們需要將鴨子解封,可使用new
關鍵字例項化鴨子物件。這樣我們便得到一個全新的鴨子物件。
new
關鍵字具體幹了啥? 可參考如下程式碼:
var obj = {};
//取得該方法的第一個引數(並刪除第一個引數),該引數是建構函式
var Constructor = [].shift.apply(arguments);
//將新物件的內部屬性__proto__指向建構函式的原型,這樣新物件就可以訪問原型中的屬性和方法
obj.__proto__ = Constructor.prototype;
//取得建構函式的返回值
var ret = Constructor.apply(obj, arguments);
//如果返回值是一個物件就返回該物件,否則返回建構函式的一個例項物件
return typeof ret === "object" ? ret : obj;
物件中的this
對於this
問題,很多初學者被這個this
指向搞得暈頭轉向。其實搞懂this
我們只需要記住一句話誰在呼叫它,它就指向誰,this指向當前呼叫它的執行環境
經典例子:
var obj = {
foo: function () { console.log(this.bar) },
bar: 1
};
var foo = obj.foo;
var bar = 2;
obj.foo() // 1
foo() // 2
js中的資料型別分為基本資料型別和引用資料型別。基本資料型別是按值訪問,引用資料型別是按引用訪問。物件將所有的引用放入棧將所有的值放入堆,要獲取一個物件值,需要先獲取物件引用,然後根據引用找到對應的值。如果引用對應的值是一個函式,由於函式是一個單獨的值,可以存在不同的執行上下文環境。那麼問題來了,同樣的函式在不同的環境下呼叫,我們如何在函式內部獲取當前執行環境呢?沒錯,this
的出現正是為了解決此類場景問題。
總結:一堆屬性和行為聚合到一起便構成了一個最基本的物件。可通過new關鍵字來例項化一個物件,由於執行環境的不同,物件內部的this指向也不同。在呼叫物件方法時,需要注意一下this的指向問題。
物件系統
上面我們提到了,一個基礎物件的構成。但是在我們實際開發當中遠遠比這複雜的多,往往是多層物件的巢狀或者多個物件通過某個對映檔案相互關聯又或者一個物件繼承自另一個物件... 從而去構建一個更龐大的物件世界,解決更復雜的應用場景,我們把這種複雜物件稱之為 物件系統 ,把這種思想稱之為 物件導向程式設計
顯式原型(prototype)
概念:每個函式上都有一個預設的prototype屬性使您有能力向物件新增屬性和方法。
function people(name) {
this.name = name;
this.say = function () {
console.log(`hello!我是${name}`);
};
}
people.prototype.kungfu = function () {
console.log(`我是${this.name},我會中國功夫`);
};
const qad = new people('秦愛德');
const zs = new people('張三');
console.log(qad);
console.log(qad.say());
console.log(qad.kungfu());
console.log(zs);
console.log(zs.say());
console.log(zs.kungfu());
以上程式碼建立了一個people
建構函式,在它內部新增了一個name
屬性和say
方法,在它的原型上新增了一個kungfu
方法
如何理解內部屬性和原型屬性呢?
這裡我們可以藉助css
樣式來便於理解
<style>
.test{
font-size:24px
}
</style>
<p style="color:red" class="test">哈哈</p>
以上我們建立了一個標籤,並向標籤新增了一個內聯樣式和外部樣式,對齊建構函式的話,內聯樣式對應內部屬性,是跟隨函式獨有的,外部樣式對應原型屬性,可以是公共的,可在多處使用。
由於每次new
一個新的建構函式,內部屬性都會重新生成,而原型屬性則不會,所以這也避免了記憶體上的浪費。並且可以基於原型實現原型繼承操作。
構造器(constructor)
概念:每個物件都會預設一個contructor
,並指向當前原型物件的建構函式。
console.log(qad.__proto__.constructor === people); // true
一圖勝千言
總結:每個函式上都會自帶一個prototype原型,在原型上新增的屬性可以共用,函式即物件,物件自帶屬性constructor指向了這個建構函式
隱式原型(proto)
概念:每個物件都有一個_proto_
屬性,指向了建立該物件的建構函式的原型。
console.log(qad.__proto__ === people.prototype); // true
萬事萬物皆物件,函式也是一個物件,只要是物件,就擁有_proto_
屬性,所以_proto_
在構造器和原型之間建立了一個連線,通過由內向外在構造器中找到原型的屬性和方法。
原型鏈
當我們建立了一個建構函式,並訪問裡面的某一個屬性時。會先從建構函式自身去找,再從顯式原型(prototype
)上去找,再從隱式原型(__proto__
)上去找,再從object
的__proto__
上去找,直到null
。有值就返回相應的值,沒有就返回undefined
,我們把這個由內向外的查詢過程稱之為原型鏈
一圖勝千言
用好物件導向思想
上面提到我們可以通過使用物件去構建現實世界的模型,並將複雜問題簡單化。要想運用好物件導向思想,我們需要牢記物件導向的三大特徵和幾個原則
三大特徵
1:封裝
中華文化博大精深,將詞語拆分之後,發現更好理解了
封:封存(將一系列行為、屬性、業務邏輯等等封存起來)
裝:包裝(提供一個容器來存放封存起來的程式碼,包裝之後,對外輸出)
封裝裡面還有一個概念叫做抽象,拆分之後也很好理解(把”像“的東西抽出來)
結合起來就是:我們把相似雷同的一堆屬性、行為、邏輯抽離出來,存放到一個包裝物件裡面,控制好入參和出參便於他人呼叫,這就是封裝。
2:繼承
繼:繼續(繼續延續下去)
承:承擔(承擔延續下來的重任,併發揚光大)
結合起來就是:子類繼續沿用父類的行為或屬性,併合理改造擴充業務,輸出新的物件。頗有點子承父業,青出於藍的意思。
3:多型
多:多種
態:狀態 / 形態
結合起來就是:同一個例項物件在多種狀態下有不同的展示形態
簡單理解就是一個函式通過入參不同,可以得到不同的輸出結果
幾個原則
1:單一職責原則
一個類或者一個函式實現功能要單一,不能雜亂無章,越純粹越好。一旦函式變得不純粹了,內部實現多個功能。當我們在多處地方使用這個函式的時候往往會因為不夠純粹而多寫很多相容程式碼。
2:開放封閉原則
一個類在擴充性方便應該是保持開放的,對更改性應該是封閉的。比如我們封裝了一個函式,應該儘量預留好口子,以便日後新功能迭代,而避免直接更改之前已經寫好的程式碼。
3:里氏替換原則
里氏替換原則主要是用來約束繼承的,子類可以擴充套件父類的功能,但不能改變父類原有的功能。如果子類不能完整地實現父類方法,或者父類的某些方法在子類中已經發生“畸變”,則建議斷開父子繼承關係,採用依賴、聚集、組合等關係來代替繼承。
4:依賴倒置原則
上層模組不應該依賴於下層模組,兩者都應該依賴其抽象。簡而言之就是面向介面開發,每個類都提供介面或者抽象類,抽象類往往是比較穩定的,當下層細節發生變化時,不應該直接影響上層。細節依賴於抽象,只要抽象不變,程式就不要變化。
5:組合聚合複用原則
在程式碼複用時,要儘量先使用組合或者聚合等關聯關係來實現,其次才考慮使用繼承關係來實現。
6:高類聚低耦合
顧名思義就是高度類似的東西要聚集起來,低相似的東西不要將它們耦合到一起
js本身就是一門物件導向程式設計的語言,在我們的日常開發中,每時每刻都在享受著物件導向給我們帶來的程式設計體驗。
感謝
歡迎關注我的個人公眾號點選檢視:前端有貓膩每天給你推送新鮮的優質好文。回覆 “福利” 即可獲得我精心準備的前端知識大禮包。願你一路前行,眼裡有光!
感興趣的小夥伴還可以加我點選檢視:微信:yuyue540880拉你進群,一起交流前端技術,一起玩耍!