JavaScript進階(九):繼承的意義

上一篇博客中,我們舉了一個員工系統的例子,當然,我們平常真正去做繼承的時候,不太會這麼去做。

我們更多的,其實是希望能夠讓一些可視化的組件,來完成繼承的工作,這個是更重要的。

接下來,我們再來搞一個例子,我們希望的是,可以用繼承來完成一些組件化的工作。

 

比如,我們想要來渲染一些組件出來:

我希望的是,我自己能夠定義一些 HTML 的組件出來。

我們都知道在 HTML 裏面絕對沒有 Cmp1 這個東西,而我希望的是這個 Cmp1 的東西,它可以以一個類的形式存在。

我在 script 裏面寫完類,上面就可以直接用,所以,這中間必然有一個過程,就是渲染這個過程。

所以,這裏需要先補一個函數,我們希望有一個 render:

這個 render 的作用非常的簡單,我希望把這個 HTML 的元素給它渲染成爲我真實的組件。

 

那麼具體來說,我們應該怎麼做呢?

我們需要兩件事:

第一,我們需要 render 這個函數。

第二,我們需要這個組件本身。

而且你還要注意一件事,我們不會只有 Cmp1 ,將來我們還會有別的組件,比如 UserLogin,這都有可能。

那麼這些組件跟組件之間,它們必然是有一些公用的東西。

比方說,對狀態的管理,對數據的管理,對渲染的一些操作等等,肯定會有。

所以如果我讓它們完全獨立來寫的話,那麼就會直接導致我將來所有的組件,它都需要實現自己的一套東西。

這樣好嗎?肯定是不好的。

 

拋開這個問題不說,首先,我們先考慮一個問題,這個 render,它需要一些什麼樣的參數呢?

我們可以用一個 json 來裝,這樣的話會方便一些:

然後我現在這麼來渲染,首先,我得告訴它幾件事:

第一,我得告訴它,你要渲染哪個元素吧?

你不能說,整個頁面裏所有的東西你都給我渲染了。

那樣性能上扛不住,而且萬一我在這個頁面裏有一部分是用這個來做,有一部分是用 react 來做,還有一部分是用 vue 來寫,那這時候它們是不是就打起來了啊?

所以在這,我希望給它傳一個參數叫做 root,這個 root,就代表了你要渲染誰,比如說我希望你渲染 div1:

第二,現在這個 Cmp1 和 UserLogin 等等這一系列的東西,如果我不告訴 render,它知道去哪找嗎?

它絕對不知道,這個很正常,因爲我的組件到底在哪,這個得我傳給它,它自己不知道。

所以我在這可以弄一個 components(名字叫什麼無所謂,這裏用 vue 裏面大家比較熟系的字段)

那麼這時候,我可以傳 2 個東西進去:

一個是我的 Cmp1,一個是我的 UserLogin。

現在我有 2 個組件傳給它了,我希望的是,有了這些東西之後,你可以給我完成渲染。

 

接下來,這 2 個類的內部怎麼實現,我們再說,我們先來考慮考慮這個 render 怎麼寫。

那麼在這個 render 裏面,首先我們需要做幾件事:

第一,你給我傳過來的那個 root,我能直接用嗎?

我真正要做的是要操作這個元素它裏面的東西等等一系列的,你給我個字符串,我沒法用啊。

我們是不是要把這個 root,給它搞成一個真正的元素,把它給選出來。

 

然後接下來,我們是這麼來做的。

首先,我是不是得先把那個 root 給它搞定啊。

當然這時候有問題了,我在這裏有沒有可能傳字符串?有沒有可能我選好了在給你?

所以這個時候,我們既然是一個庫,那我們希不希望它可以適應各種不同的情況?當然希望。

那麼我們可以做一個小小的判斷:

如果這個 root 它是個字符串,那麼說明用戶給我傳的是一個選擇器,那麼我就來用 document.querySelector 來選。

並且,我們還要檢測下,用戶傳給你的東西,一定寫對了嗎?

 

所以,我還要判斷下,這個 root 如果是空,那不好意思,說明我找不到你要的東西,就直接拋錯。

 

然後,如果你給我的 root 是個元素,那我就不用選了,直接拿來用。

如果既不是字符串,也不是元素,那我就不能要了,因爲我也不知道它是個什麼玩意。

所以我就需要拋出一個錯誤:

這個也是我們前面博客說的:

如果你希望你的程序好調試,那麼你就要在參數這個階段,把很多東西給它檢測好了。

 

那麼現在我們不管別的,這個 root 我們是不是已經扒出來了。

然後現在我想幹什麼?我現在想渲染。

具體來說怎麼渲染呢?說的直白一點,我希望可以去找到這些所有自定義的東西:

說白了,如果是個 div 啥的,這個我不管:

div 啥的,HTML 本身就有,我希望管的是我這些自己自定義的東西。

比如,我又加了一個 Cmp1,我希望找的是這些東西:

那這時候有個問題,我咋知道哪裏有自定義的東西呢?

所以 render 裏面的第二步就是,找出所有自定義的元素。

 

那麼怎麼做呢?首先,找出所有自定義的元素,之前的一步應該是什麼?

是不是先把所有的元素都拿出來,然後在判斷你是不是自定義的,對不?

那怎麼把它裏面的元素都拿出來呢?我們可以這麼做:

elements 代表 root 裏面所有的子元素。

 

那麼這個時候,我們怎麼區分它是不是自定義的元素呢?

其實簡單,我們可以直接看它的 constructor。

這裏需要注意的是,elements 是一個類數組,所以我們需要一個轉換。

 

那麼,爲什麼是 HTMLUnknowElement?因爲它確實不認識。

所以在這裏,我們是不是可以做一個判斷:

那麼接下來,剩下的事要幹什麼?

剩下的事,是不是要把 element 這個東西,給它創建出來,渲染出來等等這一系列的事。

 

那麼有一個問題,我還得再來判斷一下它的名字是什麼。

比方說,我見到了 Cmp1,我是不是就要去 new Cmp1?我見到了 UserLogin,是不是就要 new UserLogin?

 

那麼這時候我們怎麼辦呢?

其實它裏面有個東西叫做 tagName。

我們應該知道,或者見過,元素是有 tagName 這個東西的。

但是有個小問題,tagName 是全大寫的,這個是 HTML 的規定。

所以這時候,就又有一個問題了:將來是要它去跟我 components 裏面的東西去比較的:

所以,我們可以全都轉成小寫,或者大寫:

那麼這個時候,你可以看到,它們就可以找到了。

 

到現在爲止,我們已經能夠識別我們要的東西了。

當然,現在只是一個名字,我們真正要的是那個組件本身,是那個 class。

所以我們可以直接從 option.components 裏面來找:

那麼現在你可以看到,我們就找到了真正的組件本身,也就是 class。

 

那接下來我要幹什麼,組件是不是要創建出來,並且創建完之後去渲染它?

所以我就來 new 一個 CmpCls:

那麼我們就得到了一個創建好的組件 cmp,然後接下來要幹什麼?

接下來我們是不是就希望,可以去調用它的 render 方法:

 

那麼這時候有問題了,我們現在又在用多態了。

我希望的是,你的這些組件 Cmp1 和 UserLogin,它們的 render 肯定自己搞定,並且各不相同。

但是,既然我用,就得有兜底的。

那麼,爲了這個目的,我們希望所有的類都是有一個共同的、公共的父類。

這裏,我就叫它 Component:

注意,這個 Component 它本質上應該是一個抽象的。

爲什麼它要是一個抽象的?

因爲你弄不出來一個所謂的組件,你只能很具體的,比如放個用戶登錄的 input,放個自定義的按鈕。你就放一個組件,這個是不應該能出來的。

 

然後我就做一個事,我希望 Component 裏面有一個 render 方法:

這樣,Cmp1 和 UserLogin 裏面,就算不寫,也會有一個 render。

然後我在來兜個底,這裏邊直接拋個錯:

那大家可能會覺得特別奇怪,你這個 render 裏面直接報錯,這咋玩啊?

爲什麼要讓它直接報錯?

其實很簡單,就是說,只要子類 Cmp1 或 UserLogin,只要它沒有重寫 render,我就報錯。

因爲它沒有重寫 render,沒有覆蓋 render,是不是就會用到父類的 render 這裏來?

用到我這來,那我就直接報錯,這樣不就間接的讓它變成一個不可用的抽象類了嘛。

 

所以,我所有的這個類,所有的組件,全都是從 Component 裏面繼承出來的:

而且我還要求,你必須得覆蓋我的 render。

那如果我就不寫 render 呢?

可以明確的看到,它就會給你報錯,就這麼簡單。

 

那麼現在我們加上 render,比方說,我這個 render 是這樣的:

創建一個 div 標籤,內容 = 我是 xxx。

這個時候,你可以看到,是不是就不報錯了。

因爲,只有當你沒有覆蓋我這個東西,用到父類去了,它纔會報錯。

 

但是現在你可以明確的看到,在頁面上,東西還出不來,爲什麼?

因爲這裏我們僅僅是渲染了,渲染完了之後,return 的結果,用了嗎?並沒有用。

那這個結果幹什麼用?我們是不是得讓它進到頁面裏面去啊。

那怎麼進到頁面裏面去?

說白了,是不是就把我這個 return 的結果,給它替換到上面所在的位置上去:

這樣,就可以完成渲染了。

replaceChild 它的作用就是用來替換掉一個子元素。

就是說,我要把這個 render 的結果,拿來替換掉我原來的 element。

那麼現在這個時候,是不是就出來了:

 

大家一天到晚在用的 react、vue,它們裏面是不是都帶一個 render?

其實它們裏面的 render 總體就是這個感覺,當然還有些其他的東西,但是大概就這麼個事,所以也不是特別複雜。

當然,這個我們肯定有很多後續的事,比方說 Component 裏面就只是一個 render 這麼點事?

不可能的,我們還有狀態的管理,狀態變了我們還要重新渲染,還要加事件等等一大堆,但是總體就這麼個東西。

 

在這裏,我們重點講的不是 render,後面的博客我們還會詳細的說,這個版本實在是太爛了,這個先不管。

我們重點想說的是,我的 Component,可以提供大量的、公共的東西,現在我們沒加那麼多特性。

比方說數據的管理,狀態的管理,事件,我都沒加。

如果加了,我是不是可以直接在 Component 裏面來改,改完了之後,是不是所有的這些真正的子組件就都有了。

這就是繼承的意義。

 

 

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