JavaScript進階(一):什麼是面向對象,什麼是面向對象思想

這篇博客講解的都是些概念性的東西,略微枯燥,如果不感興趣,可以跳過。

(吐槽一句,概念性的東西真不是我們這些普通人玩的,自己都寫吐了)

 

面向對象這個東西,大家平常多多少少肯定是用到的。

但是第一,平常在工作裏面自己動手去寫原生對象的機會並不是那麼多,基本都是用別人寫好的框架,最多去繼承個別人的什麼東西之類的。

然後第二,面向對象這個東西,它不是簡簡單單的,我寫個 class,然後把它 new 出來,裏面放幾個方法就完了。

實際上來說,它涉及到很多層面的東西,比方說,我到底要如何去看待一個對象,然後包括我這個對象到底都能做什麼,以及我想讓我的這個對象變成一個能夠響應的狀態,等等這些東西。其實全是我們接下來要說的一些事。

 

那麼直接進入正題。

首先,我們都知道一件事:

面向對象這個東西非常的常見,而且又很重要,幾乎所有的庫,框架,還是各種各樣的東西,它們都是以一個對象的形式來存在的。

那麼爲什麼?

爲什麼它們要這麼做?

這個東西跟我用函數去做,有沒有什麼實際的區別,有沒有好處?

 

那麼我們現在先涉及到第一個話題:

就是面向對象這個東西,你千萬不要覺得它很陌生,因爲我們經常會用。

比方說 React,它裏面所有的組件就是一個 class,然後在它裏面你可以去實現你具體的東西。

class Cmp extends Component {
    constructor(){
        this.state = {
     	    count: 0
        }
    }

    fnAdd(){
        this.setState({
            count: this.state.count + 1
        })
    }
}

 

還比方說 Vue,你可能會說 Vue 裏面我們可不寫 class,其實它裏面搞的 json 背後,最終也都會被封成 class。就比如:

export default {
    data(){
        return {
        
        }
    },
    methods: {

    }
}

 

實際上來說,你會發現一個事情,就是不論面向對象這個東西以何種形式呈現,到底是個組件,還是個庫,還是個什麼東西,你會發現到最後,它主要關注的就是兩件事:

一個就是狀態的問題。實際上來說,狀態有很多種叫法。

假設我是一個組件的話,那用戶現在登沒登錄,這是一個狀態。然後我現在已經獲取到的數據,這也是一個狀態。

以及我們其實還關注着另外一個東西,就是它有一些各種各樣不同的操作

比如我點了這個按鈕,有個什麼反應。我點了那個按鈕,數據多了還是少了一條,等等一系列的事。

你會發現,這些東西它們歸根到底,到了最後都是這兩件事:狀態,操作。

你說能找出脫離這兩件事的東西嗎?不能。基本上都是他倆。

所以我們其實就可以做一個最簡單的描述:

我們平常所說的對象,其實它就是兩個東西:一個是狀態,也就是數據、一個是操作。(對象 = 數據 + 操作)

 

在面向對象裏面,其實涉及到很多概念性的東西。

既然我們想扯這些概念性的東西,那我們就扯的比較的官方一些,我們來看看官方是怎麼定義的。

 

一般來講,像是數據類的東西,它一般叫做屬性。

首先,比較出乎大家意料的是,在類裏面,屬性這個東西它其實不是最官方的叫法,最官方的叫法叫變量。

這個東西,它本身就是用來表述我們對象內部不管是一些狀態還是數據等等一系列,都是靠它。

當然了,其實它還有很多叫法:有的人就管它叫狀態,也有的人叫數據。

 

然後,方法其實也不是最官方的叫法,最官方的叫法叫過程。

當然這個東西也有很多的叫法,比如方法,函數。

 

實際上來說,叫法這些東西很多,有各種各樣的叫法,但這個不是重點,只要大家能夠互相交流就可以了。

那麼,首先我們也就明白了一個事:

我們的對象,其實它的組成就是包含這兩種東西。

你所有的對象,不管是個組件,還是多大多複雜的東西,歸根到底就是它們倆。

對象組成:

變量 --- 屬性、方法、數據。

過程 --- 方法、函數。

 

然後我們在說下類(class)這個東西,它跟我們平常所說的對象有一點容易混淆。

其實真正的叫法當中,是不存在對象這個說法的,它叫做實例(instance)

 

首先舉個最簡單的例子,比如我們平常用的:

let date = new Date()

對於它來說,這個 Date 就是類,而 date 我們 new 出來以後的這個產物,它就是實例。

 

那麼類和實例我們怎麼區分呢?

其實非常的簡單。類這個東西,它本身是沒什麼功能可言的。

比如,相信大家一般都不會這麼做:Date.getTime()

因爲即使你做了也出不來什麼結果。所以我們不能直接在類上去進行一個很多的操作。

但是你卻可以在實例上來進行這個事情:date.getTime()

說的更直白點:

類,它只是一個藍圖,設計圖一樣的東西。

它本身規定的就是,你這個類,它裏面有什麼東西。

比如我有幾個屬性,幾個方法。

但是都是死的,什麼時候能用,得到了它實例化以後能用。

用人話說,就是把它 new 出來以後就能用了。

而實例是由這個類創建的,真正有功能的那個東西,這纔是實例。

當然,一般來講,絕大多數人,在說到對象這兩個字的時候,其實更多的指的是實例。

 

當然還有一個概念,也需要我們提前說明白,就是關於成員這個東西。

一般來講,你看國外的文檔,這個東西也經常性的會出現,它都是用 member 來形容這個事。

 

那什麼叫成員呢?

很簡單,比如說一個班級的成員,就包括所有的學生和老師,這就是成員。

我們的類也一樣,它的成員,說白了就是它裏面都包括一些什麼,就這麼簡單。

說的更直白點,就是我們剛纔說的屬性和方法。

只不過像這個成員它有一些比較細分的一些東西。我們也得知道一下。

 

比如說,有一類,叫做實例成員。什麼意思呢?

說白了,像這個東西,我們只有在那個實例上能夠去用到的東西。

比如說一個最簡單的例子:

我們假設 str 是一個字符串,那麼我們可以去用 str 的 length:str.length

那你有沒有試過在大寫的 String 上面用 length:String.length

其實它身上的 length 是沒有實實在在的用處的。

還比方說,我們平常可以在一個數組裏面去 push 東西:arr.push()

這個 push 也是成員。

成員既包括成員屬性,也包括成員方法,這個無所謂。

但是像這樣寫:Array.push()

肯定是不行吧?

所以像這一類的東西,我只有在實例上才能去使用的東西,叫實例成員。

 

當然,與它相對的,還有另外一個東西,叫類成員。

說白了,就是類上擁有的。

其實我們應該都用過,比如說:Math.random

可能你平常用不着,但是你知道 Math 上是有這麼一個方法的。

像 Math 這個東西,應該是沒有人把它 new 出來以後再去用的吧:new Math()

所以像這種就屬於類成員。

 

所以,字面意思:

實例成員,就是實例上面有的成員。

類成員,類上面的成員。並且它們有一個特點,無需實例化,直接使用就行。

而成員,它是一個統稱,它一般是實例成員和類成員的統稱,這個無所謂,你知道有這麼個事就行。

 

接下來,還有一個小東西有必要說一下,如果你平時經常去看一些技術文檔之類的東西,你會經常見到這個詞:抽象。

抽象這個詞,說句實話,用的稍微有一點亂。

抽象一般來說,在我們的語言環境當中,會有兩個含義:

1,當你去設計一個類,或者你去設計一個程序的時候,你往往要經過一個叫做抽象的過程。

什麼意思呢?就好比說,我現在需要設計一個關於人的資料的數據庫。

那麼我到底需要這個人,他的哪些信息進來,這個過程其實就叫做抽象。

一個真真正正的人,他的特性太多了。年齡,身高,體重等等,但並不是所有的這些數據,你全都關心。

再比如說你在網上賣東西,其實也是需要經過抽象的一個過程。就是我需要設計一下。

比如我想出售一輛汽車,因爲和車相關的東西也挺多的,那麼我到底需要哪些:比如車的牌子,什麼顏色,加的幾號汽油等等。

所以抽象它的第一個含義,指的其實是我們平常來提取這些信息的這麼一個過程。

 

然後還有另一種說法:

2,有這麼一類東西,它不提供真正的實現,比如有一種類,叫做抽象類。

這個抽象類,它並不能真的拿去用,比如說你去 new 一個抽象類,不行,它 new 不出來,因爲它本身就不允許你去 new。

你可能會問,那它有什麼用呢?實際上它的作用是提供一個公共的基類。

比如說一個最簡單的例子,像 React 裏面,你定義一個組件,它一般都要去繼承一個 Component,然後我才做具體的事:

class Cmp extends Component {

}

在這裏,其實這個 Component 對應的功能,就是一個所謂的抽象類。

 

那麼爲什麼需要這個東西呢?

因爲我需要我所有的組件,都具備一些公共的東西。不可能說讓每個類自己去實現這些東西,那這個學習成本和使用成本就太高了。

所以我們就可以搞一個這樣的抽象類拿出來去用。

那如果我們這樣做:

let comp = new Component()

我們去實例化一個 Component,這樣做幾乎沒有意義,因爲它就是一個空的,它裏面什麼功能都沒有。

所以抽象其實對於我們來說,是非常重要的一個東西,有了它作爲保證之後,我們就可以很輕鬆的讓很多的類保持一樣的特性。

 

 

然後接下來,我們就要開始非常重要的一件事,就是面向對象思想層面的東西:

實際上來說,在我們周圍,幾乎很多人都在說面向對象思想,比如你在寫程序的時候,是否就有人告訴你,要具有面向對象的思想之類的。

但是說句特別實在的話,到底啥是面向對象思想,就沒人能把這事說清楚。

我們可以隨便百度下,不管是文章還是百科之類的,你會發現關於面向對象的東西一大堆,但是看完之後也不知道在說啥。

接下來我會盡量用一個簡單的方法,來對面向對象的思想做一個簡單的介紹,然後在後面,我們再慢慢來體會這個事。

 

首先,面向對象思想,如果硬要說的話,就3條:

1,封裝。

2,繼承。

3,多態。

然後我們接下來,就來好好的說說,這三個思想到底代表什麼。因爲有了這三個東西之後,我們用起來就會很方便。

 

首先,先來說下封裝:

封裝它的一個最基本的目的,是爲了保護我們類當中,實例當中的各種成員。

這時候大家可能很奇怪了,你說保護杯子,我拿一堆泡沫給它包起來,防止它摔了,這我能理解。但保護成員怎麼理解?

其實我們在寫代碼的過程當中,不論你是用面向對象的思想還是其他什麼思想,隨便你怎麼折騰,但是你有一個最終的目的:是爲了讓這個程序出錯少,效率高,易於調試,將來好維護等等。你用什麼都是爲了這個目的。

那麼接下來,我們就必須得面對一個現實:程序都是人寫的,是人就會有一些問題。比如說容易偷懶,容易有一些僥倖心理。

比如,這個他不讓我這麼用,沒事,我這樣用可以出來,無所謂啦,出來就好了。就類似於這種感覺。他會有一些偷懶,僥倖這樣的一些心態在裏邊。

 

實際上來說,可以通過很多方式來解決。比如說所有的代碼在提交的時候,讓組長或負責人檢查一遍,然後一旦發現有違反編碼規定的行爲,然後就罰錢啥的。但這個並不是最好的,因爲這樣負擔會很大,負責人也不用幹啥事了,就每天看提交上來的代碼。所以說最好的方式就是,不要靠人。

那麼我們就需要的是,讓代碼本身就可以去有一些規則:我這個東西,就是隻讀的,你就改不了。

實際上來說,一個完整的對象,它裏面的很多的屬性,很多的方法,它們之間並不是獨立的,它可能互相之間是通的,是有關係的。

比如說,我規定了你就得通過我給提供的一個方法,比如說 addItem。

如果你要想加一個新東西,你就得走我這個方法,你不能直接往這數組裏面 push。

那麼我問大家一個事,說了就好使嗎?不一定。我們必須得通過一些其他的手段。

這個是封裝的第一重含義,就是不要讓用這個對象的人,有一些去亂搞的機會。

 

然後第二個,就是數據的隱藏。什麼意思呢?

比如說上面爲什麼不讓你直接的去改這個數組,直接 push 一下,大家都方便,對吧。

很簡單,因爲你去修改這個數據的時候,我是要同步的去修改我其他的東西,因爲數據之間是有關係的。

然後,如果說我的數據真的暴露出去了,就有人有機會去改。所以我需要去把這個數據隱藏起來,你必須得通過我的方法來改。

當然這時候又有另一個小矛盾,就是我們都搞成方法,這個又很麻煩,比如我直接讀就行了,你還得給我弄一個 getXXX() 一大堆啥的,很麻煩。所以,我們如何讓它又簡單,又能起到保護的作用,這是一個問題。

 

然後封裝還有一個事,就是我們可以讓他去強制規定一個訪問的權限。

比方說,我有一些狀態,我就是不允許你改,我給你看可以,但你不能改。或者這個東西你連看都看不了,你只能通過我的函數怎麼怎麼樣,就是這樣一個感覺。

 

當然,整個東西,最終的目就是爲了可以讓你這個代碼,更易於被人理解。

實際上來說,我們都知道一件事,就是你不可能把整個公司的項目,它裏面的每一行代碼,全都看過,全都能理解,這個幾乎是不可能的。

所以說,我們必然涉及到一個問題,就是說,我得去用別人去寫好的,實現好的代碼。

那這個時候,這個類,它本身易不易於理解,就很重要了。

封裝在一定程度上面,還可以便於別人去理解。

 

所以,面向對象的第一個思想叫做封裝。

什麼叫封裝?封裝的目的就是爲了讓別人不能輕易的去破壞你裏面的東西。我裏面的東西你不能直接碰,你必須得通過我提供的方法來操作,按照我的那個要求來走,這樣就可以起到一個保護的作用。

封裝:

1,保護成員。

2,數據隱藏。

3,強制訪問權限。

這三個東西其實就是封裝裏面最重要的三個基本思想。

 

然後第二個,繼承。

假設你只用面向對象,不用繼承,每個類我都是新寫的,那麼這個時候,你幾乎只能發揮出這個面向對象的20%左右的功能。所以說繼承這個東西它是非常重要的。

那到底什麼是繼承,我們從來沒有給它下過一個很標準的定義。

很簡單,那就是任何一個類,可以不用從零開始寫起,而是可以直接在一個已有的,原有的類的基礎之上,再去給它做一些修改,做一些添加,或者怎麼怎麼樣,這個就叫繼承。

說的簡單點就是,父類的東西,我直接拿過來,子類就不用在實現一遍了,這就是繼承。

 

然後繼承的目的是幹什麼的?

非常簡單,第一個就是爲了重用代碼。說白了,就是父類裏面寫過的,子類不用在實現一遍了。

然後第二個好處就是,你無需去修改父類。

實際上來說,繼承有一個問題,就是如果父類本身不夠用,需要一些新的功能,那我們直接去改不就好了嗎,爲什麼要去繼承呢?

很簡單的一件事,第一,並不是所有的類,你都有權利去改。

比方說我現在這個類是來自於某一個庫裏面,就比如 React 裏面的 Component。我就覺得缺一些東西,想給它加一些東西。

那麼你能跑到人家庫裏邊去改人家的代碼嗎?行,但是不太好。

爲什麼呢?因爲將來只要一升級,就把你修改的代碼給覆蓋掉了,如果還想保留,那你又得改一遍。所以每次更新你都得搞一遍,這個東西就廢了。

所以說我們修改父類有的時候是做不到的,這是第一種可能。

還有第二種可能,就是你可以修改,它也是你自己的代碼,但是最好別改。這種情況出現在什麼地方呢?

比方說,我這有一個類,它已經用了好幾年了,不說它多好多壞,至少經過時間檢驗沒什麼大毛病,因爲有毛病早就提出來改完了,不會說用了這麼多年也沒再出過事。

那麼像這種類,你不要輕易的去動它,因爲你一旦動了,就有可能引入一些新的問題,有可能出現一些新的BUG,所以說像這些情況,都是我們不應該去修改父級的情況。

所以在這時候,你就可以去繼承。

 

那麼說到繼承,我們又有 2 個分支的小概念:

1,多重繼承。

這個是在人類社會中絕對不可能發生的事,就是一個人有多個爹,並且都是親生的。

說的更直白點,就是在我們的面向對象的過程當中,每個類它可以同時具有多個父類。

首先大家可能會覺得奇怪說,我爲什麼要有多個父類?因爲你可能需要多個父類的特性。

比如還是 React 裏面的例子:我現在有一個父類是 Component,它給我提供了很多的功能,比方說幫我提供了一些渲染,幫我提供了一些操作狀態等等,這樣的一些方法。

但是我還有另外一個父類 Ajax,這個父類裏面給我提供了很多其他的一些功能,比如說數據的管理,跟服務器之間的數據交互等等一系列。

假設你現在的組件之內,這兩種東西都需要,那麼你就可以用多重繼承來寫。

class Cmp extends Component, Ajax {

}

這個就叫多重繼承。

當然,這個在 js 裏面是不直接支持的。爲什麼呢?

因爲這個多重繼承會讓結構和狀態變的特別混亂,所以我們一般不會直接去用它。相反,我們都是用其他的一些手段來完成目的。

 

接下來還有另一個概念:

2,抽象類。

這個在上面我們也提過。就是說,我這個類,本身它不具備實際的功能。

你說讓我去幹活,你 new 個我,白 new,沒有意義,跟 new 了個 json 一樣,空的。

但是我提供了一些公共的一些屬性或者方法,一般是作爲基類或者是父類來使用。

 

所以,繼承的好處:

第一,是爲了最大限度的去重用父級的代碼。

第二,無需去修改父類。

當然順便一說,重用父級的代碼並不一定只是爲了省事,還有另外一個目的。

就是萬一將來父類的代碼發生了任何修改,子類不用動,就可以享受到這種變化,這也是一個目的。

 

最後,還有一個多態。

首先,什麼叫多態?很簡單,它其實是一種抽象。

簡單來說,就是說我現在這個類,我不提供真實的實現,至於到底誰來實現,我不管,反正將來總有人幹,總有人會現實這個東西。

比如舉一個最簡單的例子:

假設,我們現在有一個類,是專門用來描述我們公司裏面的員工。首先,它肯定是個抽象類,你公司裏面不可能有真正意義的員工。

大家可能覺得奇怪,不對啊,我們公司到處都是員工。

實際上來說,你們公司裏有的,只能是具體哪個人。你比方說有個前端,有個測試,有個運維等等。

其實你都是這些具體的,員工本身就是一個抽象概念。

然後這個時候我們可能碰到一個問題,就是工資管理系統。

我們不得不承認,不同的崗位,它計算工資的方式完全一樣嗎?差的還蠻多的。

那麼在這種情況下,我要怎麼辦?

我先判斷下,if 如果這個員工的 type === 前端,那我就用這套方式來算。

然後 else if 如果這個人是測試,我就這樣這樣算。else if else if 一大堆,這個是不是就亂了。

而反過來,如果你用了多態的這種寫法之後,沒關係,我現在就是把你們都當做員工,這個抽象概念來理解。

然後我就直接去用你們提供給我的那個計算的方法,我不去管到底怎麼回事,你自己怎麼算是你自己的事。

這個其實就是一個抽象的過程。

我們如果合理的去使用多態這種思想的話,其實是有3個好處的:

1,很大程度的去減少你的代碼量。

2,也可以讓你的邏輯變的非常清晰。

3,同時也能夠幫助我們來簡化我們的問題。

 

那麼到此,這些概念性的東西就講完了。

平時老有人提面向對象,什麼意思,幹什麼的,什麼叫面向對象啊?

我們分了幾個點來說,這裏稍微來回顧下。

第一,所謂面向對象。對象是什麼,就是屬性和方法。你不要把它看得太複雜了,就這2個東西,多了沒有。

第二,面向對象的過程當中,其實有很多的一些概念性的東西。

比如什麼叫類,類只是一個藍圖,只是一個設計圖。它沒法直接的起任何作用,你需要把它實例化出來才能用。

實例化,用人話來說就是new。new 出來就叫實例化。

然後,類,實例化了之後,就會變成一個實例,這個實例是你可以真正去用的,以及它身上所有那些東西都統稱爲成員。

 

然後我們還說了一個很重要的東西,一直聽人說面向對象思想,到底怎麼樣才叫面向對象思想?

第一,封裝。你要保護好自己的狀態,不要讓別人隨便去碰。

第二,繼承。我要儘可能的在前人的基礎之上去做事。所以說,繼承一定是在別人已有的基礎之上,不論是一個庫所提供的某種東西,或者是你自己的其他部分代碼的某種東西,都可以使用繼承。

第三,多態。它其實是一個挺抽象的概念,你很難直接的感覺到,你可以簡單的理解爲,它能幫助我們簡化問題。

 

整個面向對象,它的目的非常簡單:

第一,其核心目的就是爲了防止人犯錯誤。

第二,儘可能的去重用各種各樣的代碼。

 

當然,我們現在說的這些東西,實際上來說,大家可能都很難想到具體來說是怎麼樣的。

沒關係。接下來的博客,我們就來看看,寫成代碼,長成什麼樣。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章