JavaScript進階(三十二):如何把真實的 DOM 抽象成虛擬 DOM 樹(二)

上篇博客中,我們的 VText 還沒開始,這裏我們繼續。

那麼實際上來說,對於一個 VText,你覺得我關心的是啥?我關心的是字。

所以在這個時候,我身上的屬性就一個東西 _text,那麼它的值就是對應文本節點的 data:

那麼這時候,我們就來看一下:

你可以看到,雖然 _text 有了,但是有些奇怪,所以這時候,我們有幾個問題。

首先,空文本節點,其實我不想留着,我想給它刪了,因爲沒有意義,我們不需要去處理。

所以我們就直接來,加個 trim:

可以看到,去完空格之後,_text 的值就比較正常了,第一個和最後一個就是空的了,中間那個也只剩下字。

那麼到這爲止,我們已經算是有了一定的成效了。

 

但是有一個小問題,就是我不想讓空的文本節點存在。

那麼我們就可以在 VElement 裏面做個判斷:創建完文本之後,我並不會直接的就把它塞進去,而是做一個判斷,如果這個 node,它裏面的 _text,不是空的,換句話說,就是有值的,我才把它 push 進去:

當然這時候可能有人說,你前面的博客不是說過,儘量不要處理別人的私有成員嗎,那你怎麼還用 node._text?

其實是這樣的,首先 VNode、VElement、VText 這幾個玩意,其實是同一套庫內部的東西,所以這時候動,其實沒事的,因爲本質上,我其實是把一段大代碼,分成了好幾份來寫,作者都是我一個人,所以這個倒問題不大。

然後,反過來,你就說不行,不能操作私有成員。沒問題,那麼這時候,我們可以在 VText 裏實現一個方法 isEmpty,在這來判斷一下,它是不是空的:

然後我們這邊是不是就可以用實例上的 isEmpty 來判斷了呀:

可以看到,這樣也是可以的。

但是說實話,這個庫因爲都是我們自己寫的,所以就懶得去搞了,反正別人又不用,我們自己用問題不大。

 

那麼這時候,我們再重新來看看現在的結構:

你可以看到,我只有 3 個子節點了,沒問題,挺好的。

所以到目前爲止,我們就算是完成了,讓真實的 DOM,映射爲一個虛擬 DOM,這樣的一個工作。

不過接下來,我們還有很多很多事要做,這只是第一步。

 

我們接下來要做什麼呢?

首先,就是我現在希望讓這些屬性有所區別:

比如,不論是 vue 還是 react、angular,它所帶的那些自定義的屬性,也就是指令,其實我是需要把這些指令給識別出來的。

也就是說,這種指令類的東西,我一定是要單獨的提出來,因爲我要處理它。

以及,事件類的東西我要不要提出來?要,我也要處理它。

所以在這種情況下,我們不能只有 _attrs,還得有一些特殊的屬性,指令或事件。

 

所以,我們不光有 _attrs,我們還有指令 _directives:

那麼這時候有一個問題了,同樣是屬性,你怎麼知道它是正常屬性,還是指令 _directives?我們都知道,有標誌的。

比方說,但凡是以 v- 開頭的,我就認爲是指令 _directives。

而冒號 : 它其實是個簡稱,它全程叫做 v-bind:,它其實也是以 v- 開頭的,這就是標誌,比如:

所以這個指令 _directives,它就是所有以 v- 開頭的。

當然,我們就不以別人的標準來做事,這裏我用 z- 開頭,然後 @ 我們就用 + 號來代替。

因爲你想選什麼作爲標誌,都是自己定的,無所謂,只要有個標誌就行。

 

然後再接下來,我還需要一些東西 _listeners,就是它上面各種各樣的事件:

這些事件標誌非常的簡單,就是以 + 號開頭的,或者以 z-on 開頭的。

其實就是一個簡寫和非簡寫的關係,比如:

 

那麼接下來,首先,我們就想要來識別出所有的指令 _directives。

注意,這個 _directives,我們是不用從零開始識別的,是不是從 _attrs 來就行?

說白了,這個 _attrs,我希望它裏面放的是大雜燴,也就是,所有的屬性在它裏面都有。

而 _directives 呢,它就比較的專用,是專門用來放指令的,而 _listeners,就是專門放事件的。

 

那麼在這時候,有一個問題,如果我把所有的邏輯,都寫到構造函數 constructor 裏面,那幾乎是一場災難:

我們都知道一件事,就是,對於一個類來說,它的構造函數應該極度的精簡,爲什麼?

這樣做有一個巨大的好處,就是別人,包括你自己,在看這個類的時候,一目瞭然。

如果你這寫了很大一堆,別人一眼看過去,根本就不知道是啥,你自己維護,也要找半天。

所以說,我們需要把它稍微的抽一下,精簡一下。

 

那麼我們就直接來,首先,我們來一個命名空間,也就是用匿名函數自執行來形成閉包:

那麼這時候就有一個問題了,外面的 VElement 就用不了了。

所以,我們可以掛在全局 window 上:

那麼我們爲什麼要這麼做?

原因很簡單,就比方說,創建子元素這個事,我希望用一個函數來搞定它,比如我就叫它 createChildren:

當然這時候可能也有人奇怪,你爲什麼非要把它放到外面,搞成個函數?

直接放到 class 裏面當個方法不就完了嗎?就像這樣:

其實很簡單。

首先,我這個方法 createChildren ,希望用我這個類的人來調用嗎?絕對不希望。

所以我們一般情況下的做法,都是在名字前面加個下劃線,來表示它是私有的,你別用這個方法。

但這個方法有幾個問題。

首先,你把這些都放在類上,類的體積是不是會變大,那這時候性能就會有所下降。

因爲一個東西越大,它就越慢,這是理所當然的道理,也是事實。

比如,有興趣可以試試,一個 json,裏面就一個東西,跟它裏面有 100W 個東西,從它裏面取一個元素出來,時間是不一樣的。

所以,這時候我都放到類裏面,首先就會降低性能。

而且說句實話,你這麼做了,別人就一定調不了了嗎?這就防君子不防小人。

所以,如果我想把它隱藏的及其徹底,那你作爲我的一個局部函數存在,別人就無論如何也訪問不了了,因爲是局部的,所以這種更安全。

 

那麼現在這個時候,我們就把這一塊,全部的照搬過來:

當然,裏面的細節也要修改一下。

我們需要把 dom 當參數傳進來,並且返回處理好的 children:

所以我們這樣做,整個構造函數 constructor 是不是就稍微乾淨了一些。

以及呢,我們這個 _attrs,其實也最好拿走,我們就叫它 createAttrs:

那麼現在,我的這個構造函數 constructor 就及其的精簡了:

這樣的話,別人去看你的代碼,可讀性會非常的強,他可以一瞬間就明白你大概做了哪些事。

而他再關心某個具體問題的時候,比方說 _children 怎麼來的,他就可以單獨的去看你某一個東西。

所以這樣做是非常好的,能夠增強代碼的可讀性。

 

然後繼續往下努力,我們還需要一個 createDirectives 和 createListeners:

然後這時候,我們的這個 constructor 其實已經做完了,剩下的只是填補這些東西而已。

其實你會發現,虛擬 DOM 本身並不難,我們就是在用一個對象,來表述一個真實的 DOM。

當然,我們要做到幾件事:

首先,我們得精確,別人家有,我們沒有,這不行。

然後,你要抽取你需要的那些信息,你不要的,你也不需要。

以及,我們現在所做的這些事,其實還有一個非常大的工作需要去做,就是我需要去更新它,比方說有一天,數據變了,那麼我需要同步的去更新它。

這些工作怎麼做,都是我們要考慮的,那麼我們就一步一步的來。

 

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