JavaScript進階(二十七):DOM 的編譯(二)

這篇博客,我們就來乾點 render 該乾的事。

首先,我們現在寫的這個 render,我個人更喜歡把它叫做 update:

爲什麼呢?

因爲我喜歡把 render 單獨的提出來。

render 就是 render,render 不做判斷,它就直接是搞事情的。

我這麼做其實有一個巨大的好處,是什麼呢?

我們現在寫的這個類 Damu,將來肯定是要當做父類、基類來用的。

那麼我在子類裏面,是不是隻要單純的去 render 東西就好了呀?只要單純的去覆蓋 render,我就可以做事了。

以及,我這裏的 update,是不是可以作爲一個,手動去更新的方法啊?更新就調用 update 就可以了。

 

所以,我們在 set 裏面調用的就是 update:

當然,這個對我們的功能沒有任何的影響:

可以看到,它也能觸發 render,我們只是爲了讓它更乾淨。

 

那麼到現在爲止呢,我們還有一個小問題,請問我們的這個渲染,進行多少次?

只進行初始的這一次?絕對不是。

那我們要什麼時候做?

是不是每當數據變化的時候,我們都要做一次渲染。

所以這個時候,其實我們每一次渲染,都需要這個原始的模板。

什麼意思呢?

就是說,div#root 裏的這個原始模版,如果一旦破壞掉了,比方說我把它編譯成了這個:

那以後我們還找的着 {{name}} 嗎?就找不着了。

所以,這個原始的模版我還得保留,不能破壞它。

我真正去渲染,得是把這個模板複製一份,然後去改那個副本。

這樣,我手裏永遠都有原版。

 

所以,我們在 this 身上加一個屬性,叫做 _template,當然,叫什麼名字無所謂。

這個 _template,就直接是一個文檔碎片 DocumentFragment:

那這個 _template 怎麼來呢?

我們直接做一個 appendChild,我要的就是 this._root:

那麼接下來,在 render 裏面我要幹什麼呢?

注意,我不是直接就去改模板 this._template。

我會把 this._template 直接做一個 cloneNode,這樣的話,它裏面的東西,我也能拿來。

這個,就是我們現在的 root。

 

然後,我們肯定是要對這個 root 做各種各樣一大堆的操作,具體先不去寫。

等到最後的時候,我要做什麼呢?

我要做的很簡單,就是把我現在這個的 _root,也就是頁面當中的 div#root,給它換下去:

那麼 replaceChild 的參數怎麼寫,誰換誰?

我們是把這個新編譯好的 root 給仍進去,替換掉原來的那個 div#root。

這樣的話,我們是不是就實現了更新啊?

當然,我 this 身上的_root,它也就變成了新的這個。

這就跟你家裏換燈泡一樣,舊的擰下來扔了,新的再擰上去就行了。

 

當然,到目前爲止,我們在 render 裏面只是做了一個 cloneNode。

所以頁面上是沒什麼效果的:

但是,儘管現在沒做事,但我應該有東西啊?好歹你給我個沒編譯過的東西吧,爲什麼頁面上啥都沒有呢?原因很簡單。

因爲我們原來的那個根元素 this._root,已經從 HTML 裏面摘掉了。(如果這裏沒看懂,可以看下 js 進階二十二)

所以它既然已經摘掉了,那麼它現在還有 parentNode 嗎?

那麼現在這個時候,你可以明確的發現,它有,是文檔碎片 DocumentFragment。

完全都在 DocumentFragment 裏面來搞,這肯定不行的。

 

那我們怎麼辦?很簡單,存一個不就完了嘛。

每次我們都要去重新找一遍 parentNode,本身就沒這個必要。

我就給 this 身上加個 _parent,它裏面存的,就是 this._root 的 parentNode:

然後 _parent 有了之後,我們就可以直接這麼去用了,它也方便很多:

但是,你可以發現這時候又不行了:

注意,這時候,我們的 _root 還在不在 _parent 裏面?其實早就不在了。

爲什麼早就不在了?因爲它被文檔碎片拿走了,它現在在 this._template 身上。

那怎麼辦?很簡單,現在我們先不摘。

如果先不摘的話,又有一個問題,那就是 _template 沒了。

所以在 render 裏面,我們需要先用原來的那個_root 代替:

現在我們來看看效果:

那麼現在你可以看到,就能出來了。

 

但是有一個小問題,如果我再渲染一次,那麼我們的 _root 就沒辦法用了,爲什麼?

因爲我那個原始的 _root 其實是沒保存的。

爲什麼呢?因爲在 render 裏面它已經被替換掉了,然後它就沒了,最後再賦值,它就徹底被覆蓋掉了。

所以說,DocumentFragment 我們可以先不用,但是,我這必然要有一個東西:

我這個 _template,等於最開始的 this._root,也就是 div#root,來給它克隆一份。

而且這個 _template,它永遠都不動,我每次需要新東西,都從它身上再克隆出一份:

也就是說,this._template 裏面放的,永遠都是 div#root 這個東西:

我每次複製出來,都是全新的一份。

 

那麼接下來的問題,就是我怎麼樣識別出 {{name}} 這些東西,或者說,我怎麼樣得到這些東西的內容?

所以,我們的第一件事,就是找到所有的模板字符串,也就是雙花括號 {{ }}:

那麼這個怎麼做呢?

首先我先問大家一個問題,這個 {{ }},我放元素裏面肯定是可以的。

但是,如果我們放在標籤上面,比如說 id = {{ }},這個我們認嗎?

這個我們不認。

所以,我們肯定是在文本里面去找 {{ }}。

那麼,我們就把這個 root,它所有的 childNodes,給它循環一下:

注意,爲什麼我們現在不用 children?原因很簡單,因爲我找的是文本。

當然在這可能有人說,你這就一個文本節點啊,爲什麼要循環呢?

比如說,div#root 裏面有沒有可能還有一些其他的元素,把它們給切開了?

完全有可能,現在不就是 3 個節點了嘛。

所以我們考慮到這個問題,還是要做循環的。

 

那麼通過循環,我們會得到一個又一個的 child,然後就做一個判斷。

我看看這個 child,它的 nodeType 是不是等於 document.TEXT_NODE。

如果是的話,別的我不管,我就想先把它裏面的內容,拿出來看看:

你可以明確的看到,一個姓名,一個 a+b。

 

然後注意,這玩意是個字符串嗎?

其實不是的,它是一個 text,文本節點。

這時候有人可能會說,我們直接用個 innerHTML 不就拿出來了嗎?

innerHTML 是給元素用的,文本節點沒有。

那 innerText 呢?

也沒有。

實際上來說,如果你想拿一個文本節點的內容,需要用 data:

現在你可以看到,它就是一個普通的字符串了。

 

然後我現在要做的,就是從字符串裏面,找到所有的 {{ 開頭,}} 結尾,以及它中間的東西。

怎麼做到這一點,相信大家已經想到了,正則:

但是別忘了,這個 { 和 } 在正則裏面本身就有含義,它代表的是量詞。

所以我們需要對它做一個轉義:

{{ 開頭,}} 結尾我們搞定了,那麼中間放什麼?

放 [a-z] 字符串?如果我裏面還有數字呢?再加上各種各樣的符號?所以我們這麼判斷不行,因爲一個個加是加不完的。

但是你別忘了,{{ }} 這裏面無論出現啥,有一個東西是出現不了的,就是 }}。

如果出現 }},是不是這個表達式就提前結束了呀?所以就它不能出來。

那麼在中間,我們就可以用一個特別簡單的寫法,排除。

除了右花括號 }},什麼我都能認,就這個不行:

然後別的我們不管,現在就想看看,它給我匹配出來的東西對不對:

那麼你可以看到,我們要的就是它。

然後爲了方便演示,我們現在先不管加法運算,當然,後面也會做。

然後現在這時候,我們已經有它了。

那麼接下來,是不是要把左右的花括號 {{ }} 給去掉,我只要中間的內容,那怎麼辦?

其實很簡單,直接用 substring 去掉前面 2 個和末尾 2 個就行了:

那麼這時候你可以看到,一個 name,一個 a。

 

事情還沒完,現在這個 str 有了,我們要做什麼?

很簡單,我要把 {{name}} 這個東西,替換成我 data 裏面的數據,所以我們要做的就是 replace。

首先肯定一點,我在這裏 return 什麼,它就是什麼,比如我這裏就 return 'aaa':

這個時候,你可以明確的發現,就都變成 aaa 了。

那麼我要的是什麼,我要的是,真正變成這個 str 所對應的東西。

比方說,現在這個 str 是 name,那麼我要返回的,就是 data 當中的 name。

如果我們直接寫 data[str]:

可以看到,報錯了,因爲你還沒告訴我 data 是誰。

實際上我們要找的,其實是 proxy,因爲數據都在它身上。

可能有人說,我知道了,可以 this[str]:

可以看到,是 undefined,爲什麼呀?

注意,我們上面也說過,這個 proxy,並不是 this。

this,是我當前的那個實例。

而 proxy,只是幫我這個 this 出去頂雷的一個哥們,它是對外的。

所以怎麼辦呢?我得把它稍微的存一下:

順帶一說,vue 裏面的那個 data,就是這麼來的。

 

那麼,我們是不是就可以寫成 this._data[str]?

出來以後的效果,大概就是這個樣子:

然後,我想給 a 重新賦個值:

以及我又想給 name 換個名字:

你可以看到,就這麼簡單。

vue 和 react 裏面,也都是這樣。

 

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