這篇博客,我們就來乾點 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 裏面,也都是這樣。