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

上篇博客,我們做了一些準備工作,接下來,我現在就想要一個東西:

我希望這個 VNode,可以用它來描述我現在的這個 div#root

那麼在這時候,我們就要考慮一個事:我的 VNode,需不需要某種參數?需要。

因爲所有的虛擬節點,你不論它虛了個什麼,反正它只要是,就必然會有一個東西,它需要對應於一個真實節點,這是肯定的,不然它就廢了。

 

那麼這裏問大家一個問題,一個元素需要對應一個真實節點嗎?

當然需要,一個 div 對應一個虛擬 div。

然後反過來,一個文本需要對應一個真實節點嗎?其實你想完了就發現,也需要。爲什麼呢?

比方說,我虛擬的 VText,它裏面的字變了,那我這裏面要不要跟着變?必須跟着變。

所以在這時候,你可以想到,我們的 VElement 和 VText,它們都需要一個真實的 DOM 作爲參數,要不然,我哪知道對應誰啊。

 

那既然它們都需要,我們爲什麼不放在父級呢?

所以這時候,我們就決定了, 把這個真實的 DOM 元素,放到我們的父級 VNode 裏面:

這個父級 VNode 其實它更適合的,是被抽象成爲一個抽象類,這個其實是更合適的,當然我們的 js 並不支持這個功能。

我只是想告訴大家,它本身其實沒什麼功能可言,所以它最多就是可以幫我們把這個 DOM 給存起來。

並且,我們也不太希望它是一個誰都可以訪問的狀態,要不然萬一,別人把它給改了,我們這就亂了。

所以,我們給它一個 _dom:

以及,我們前面的博客裏面也說過,一個程序,想要讓它保證健壯性的話,就一定要檢查好你所有的參數。

所以說在這種情況下,我們還得有一個 common.js,現在我們就需要一個 assert,並且以後還會填充一些其他的東西:

那麼現在有了 assert 之後,就需要來看一看,你這個 dom 到底給了沒,你要沒給,我就報錯。

以及,我還需要來檢驗一下,你這個 dom,到底是不是一個真正的元素,如果不是,也不行:

當然這時候,我們稍微的來驗證一下,寫一點就驗證一點,否則的話就完蛋了:

那麼現在這時候,你可以看到,是一個 false,所以幸好驗證了一下,不然就廢了,所以千萬別一次寫太多在測。

那麼在這時候,我們就明確的知道了一件事,就是 HTMLElement,它其實表述的是元素那種東西,但並不能表述文本 text。

其實在我們的 HTML 裏面,它有一個東西叫做 Node:

你可以看到,這個時候就是 true 了。

所以,我們的代碼驗證也需要跟着改:

那麼現在呢,我就想創建一個東西:

那麼這時候你可以明確的看到,它是一個 VElement,並且它裏面有個 _dom,指向的,就是那個最外層的 div#root。

 

那麼我們接下來,就要仔細的來思考一下了。

一個抽象出來的元素,它應該具備什麼?我們關心什麼?

首先,我們所有的東西都關心嗎?並不是的,我們只關心我們要的東西。

 

那麼我們來考慮一下,一個元素它都有什麼?首先,它有沒有屬性?

元素上有屬性很奇怪嗎?當然正常,然後你比方說,我這還可以有事件 onclick:

事件也是屬性!

那麼你仔細想想就會發現,其實一個標籤,它什麼都沒有,就只是屬性構成的,事實上也就如此。

當然,除了屬性以外,還有另一個我們需要關注的東西,就是,它是什麼標籤,它是 div,還是 span?

儘管我們沒有去抽象那個專用的類,但是如果我有一個標識,用來表明這是一個什麼東西,其實還是有用的,這樣的話,對處理起來是很方便的。

 

所以我們主要關心這幾件事:

第一,它是什麼東西,div 還是 span?

第二,我關注它上面都有哪些屬性。

第三,我還要關注一下,它有哪些子節點,這個也是很需要的。

因爲比方說,假設我現在要把這個 div#root 給移除掉,那麼我是不是也要把這些子元素全都移除掉?所以說也是需要的。

 

那麼我們在加這些東西之前,先考慮下,我是加在 VElement 這級合適,還是加在父級 VNode 合適?

首先,我們需要一個標籤,比如我叫它 _type:

然後我們是不是還需要屬性?我們就叫它 _attrs:

然後我還需要一個東西,就是我的子節點們,我們可以叫它 _children:

那麼現在我們就來考慮下,這幾個東西,需不需要往父級提?或者說的更明白一點,這幾個東西,VText 需不需要有?

我個人覺得不用,比方說 _attrs,文本節點有屬性嗎?沒有。

然後 _type 呢?不用,文本節點就一種類型,文本。

以及 _children,文本節點就沒有孩子一說,它就是一堆字。

所以說這 3 個,就是 VElement 獨有的。

 

那麼接下來,這個 this._type = 'div',我們是寫死嗎?不可能。

我們是不是可以直接從 dom 上面直接去取它的 tagName 就行了?

說白了,標籤是啥類型的,由標籤名決定。

而且這時候還沒完,注意,tagName 是全大寫的,所以在這個時候,你就可以看到,它的 _type 有了,但是是大寫的:

就我個人來說,大寫看着挺難受的,所以這裏就用 toLowerCase 來變成小寫:

以及接下來,這個 _attrs,我們寫出來一定是空的嗎?不一定,是不是得根據真正的 DOM 屬性來走。

那這時候我們怎麼樣能夠把它上面的哪些東西,給拿過來呢?

其實非常的簡單,我們可以用 attributes 來循環,把每個屬性的 name 和 value 給 _attrs 加上:

那麼,我們先看看 _attrs 有沒有正確的加上:

目前看起來都是對的,並且你會發現,它有個 :value,並且把冒號也給帶上了。

因爲 attributes,它本身就能夠去幫你把那些自定義的屬性給加進去,而且你甭管多怪都可以,包括有加號的 +click。

所以現在這時候,我們這個 _attrs 工作的非常好,什麼屬性都能進來。

 

那麼在接下來,我們還有一些 children,這時候有個問題來了。

請問一個虛擬節點 VElement,它的子節點是什麼?

是真實 DOM?不可能,它的子節點,是不是也是虛擬 DOM 啊。

 

所以在這種情況下,我們的這個 _children,就需要稍微的做點小事了。

我需要的,是把我這邊真實的 DOM,它的 childNodes,給循環一遍,那麼我們就得到了一個又一個的 child:

然後我們把它 push 進去,push 一個 new VElement:

這樣是不是就說白了,我的子級也依然還是一個虛擬節點,這就對了。

 

這時候有人肯定不樂意了,我所有的子級一定都是 VElement 嗎?不一定。

比如,這個就不是,它是文本:

所以說,我們在這裏就要做個判斷:

以及,我們考慮到萬一有什麼節點被我漏掉了,我們可以 console 一下, 看看是什麼節點:

那麼現在這時候,有了這個之後,我們再來看看:

然後你會發現,一棵樹就已經構建出來了。

所以這個東西到現在爲止,先不管活幹沒幹好,我們已經能夠把真實的 DOM 樹,抽象成爲一顆虛擬 DOM 樹了。

 

當然,現在有很多問題,比方說我們的 VText 還沒開始幹活,這個肯定不行。

然後以我個人的習慣,一般空文本節點,我是會忽略掉它的,爲什麼呢?

因爲空文本節點,你處理它幹啥?東西越少,你肯定跑的越快,就這麼簡單,既然我知道它沒用,那我就把它刪掉。

所以下篇博客,我們就給它做點事。

 

當然在最後,可能有人說,你上面的遞歸創建有問題,要是頁面大的話,不就炸了嗎?

其實很簡單的一件事,如果我們不創建虛擬 DOM,直接操作真實 DOM,會怎麼樣?

我們都知道,性能會很低,虛擬 DOM 無論如何,它的性能,一定是優於真實的 DOM。

 

所以說句很實在的話,首先在我們創建的過程當中,頁面如果大的話,比方說有幾十萬個元素,那這時候,我們的虛擬 DOM 樹也會很大,沒錯,這個一點不假。

但是,我們操作這個虛擬 DOM 樹的性能,速度一定是比不幹這套事,要快得多。

 

這個虛擬 DOM 樹說白了,最多就是佔點內存,它在性能上有極大的優勢,你頁面越大,反而我這邊的工作才越有價值,反而這邊的性能才越高。

所以你不用擔心,越大的頁面,不光不會影響性能,反而會提高性能,這是第一

 

然後第二,就是我們在創建虛擬 DOM 的時候,會佔掉一定的內存,沒辦法,任何東西都要佔內存,那麼到底有多大?

其實這個很好估計,你就估算一下,它有什麼屬性之類的,就按一個虛擬節點 500 個字節算,這其實算比較大的了。

那麼假設你有一萬個節點,那就要佔 10000 * 500個字節,滿打滿算都不到 5 M,對現在的電腦來說根本不算什麼。

 

所以你會發現,就算你真的有一萬個節點,也就只佔 5 M不到的內存空間,其實並不算大。

而且說句特別直白的話,如果你這頁面真有一萬個節點,先不管虛不虛擬,首先你這頁面估計都跑不起來,那頁面都沒了,其他的你還有啥好擔心的。

所以,真實 DOM 沒炸,虛擬 DOM 一般也不會,虛擬 DOM 它一定是比真實的 DOM 要快,雖然它肯定是喫內存的,但是它喫的不多。

 

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