JavaScript進階(二十三):實現一個循環需要注意的問題

這篇博客,我們準備拿 DOM 的東西來做點事。

實際上來說,幾乎在所有的框架當中,它們普遍都有一個操作,就是循環。在 vue 裏面叫做 v-for,在 react 裏面叫做 map。

那麼接下來,我希望的就是,我要如何來實現一個這樣的功能,根據數據來做循環。

 

比方說現在我們這有個數組,我希望的是,讓這個數組裏的東西,能夠自動的被渲染到 UI 裏面去:如果數組裏有 4 個東西,那我頁面上就有 4 個 div,如果數組裏面刪了一個,那頁面上對應的,也就變成 3 個 div。

這不就是我們前幾篇博客說的可響應嗎?所以,如果我要完成一個這樣的工作,首先我們要做的,就是如何讓一個數組可響應。

 

我們先弄一個大概的樣子:

然後,我們如果什麼東西都像以前一樣,都是 innerHTML 直接往裏堆,那肯定不行。

所以我們先放一個模板的元素在這:

那麼現在這時候,基本的樣子就有了。

 

首先,我想讓一個數組,變成可響應的狀態,那我們應該怎麼做?

當然是用我們前面說到的 proxy:

然後對這個東西來說,我們主要監聽的就是兩個東西,一個 get,一個 set。

當然在這,我們主要關注的是 set,因爲當它對我進行了一些操作的時候,我需要能夠響應它的操作,比方說,去做一個渲染。

那麼有人 set 了,我們就先 console 一下,以及對這個 arr 進行一下相應的操作:

然後我們來試一試:

你可以看到這個 set 就已經被觸發了。

 

然後我們在 js 進階十六的時候,有個東西只說了結論,並沒有深入往下扯,那就是 push。

比如,我現在想要給它 push 一個值:

是不是給我報錯了,那麼這個錯到底是怎麼回事?其實它是 2 個 set。

原因是這樣的,其實這個 push,它會映射爲兩個數組操作:

第一個操作,它要給數組增加一個值。

第二個操作,它要去改變 length。

 

所以在這個時候,我需要 return 一個 true:

什麼意思呢?意思就是說,我這個操作繼續。

 

那這個 return 是怎麼回事呢?

簡單來說,大概的意思就是,這個 set,它有一個作用,就是說,你在設置的時候,你要校驗一下,到底這個 set 是不是合法的,如果不合法,我就不再繼續往下運行了,是幹這個用的。

所以在這我們 return 一個 true,它的作用也很簡單,就是告訴這個 proxy 對象,沒問題,這事可以,我們接着往下走。

所以這也是爲什麼,我們不 return 的時候,它給我報錯,我們仔細看看這個報錯:

trap,指的就是 set 函數,也就是所謂的陷阱函數,怎麼叫都行。

它說這個東西,return 了一個假的值,所以這時候它就會給你報錯了。

而如果你真正,去 return 一個 true 的話,你會發現,就沒這問題了:

然後你會發現,它觸發了 2 次 set,我相信大家一定和我一樣奇怪,是哪 2 次 set 啊?

我們把這個 name 和 val 給打印出來看看:

你可以看到,第一次,是它設置了一個新的 3,你本來有 0,1,2,然後 push,是不是要在 3 號位加個值啊?這很正常。

然後第二次,它給了你一個 length,讓你把 length 變成 4,不奇怪吧?

那麼在這種情況下,我們的這個數組,你可以看到,它就正常的更新完了。

所以這個 set,會觸發 2 次,然後你需要 return 一個 true,表示這個操作可以被繼續下去,就完了。

 

說白了,現在這個 arr = [12, 5, 8],如果我 push 個 99。

首先,它肯定會變成 [12, 5, 8, 99],那麼這個工作,它其實會變成 2 個:

第一,我要給第三個的位置加個 99,也就是 arr[3] = 99。

第二,我這個數組的 length 還要變成 4,也就是 arr.length = 4。

所以說,push 其實會被拆解爲 2 個操作,這也是爲什麼它會觸發 2 次 set 的原因。

 

然後,這個也同樣適用於別的,比方說我現在不去 return,然後我去做一個 pop。

大家想象一下,pop 是不是也一樣啊?我是不是既要設置值,又要設置 length,所以如果我不 return 的話,它也會報錯:

而如果我 return 一個 true,那就代表這個操作要繼續下去:

所以這個其實沒啥可說的,就是說你需要做一個 return,就完了,這是 proxy 的一個要求。

 

那麼接下來呢,別的我們不管,至少現在我們已經可以做到這個數組是可響應的了。

然後我們需要做一個小操作,響應完不是賦個值就完了,我們還需要一個 render 來幫助我們渲染東西,比方說現在這個 render 我就單純的 console 一下:

然後我們加上去,先更新完數據,然後 render:

那麼接下來,我們實驗一下:

可以看到,當我們去添加或者修改的時候,都能觸發 render,但是,用 push 的時候,觸發了 2 次。

爲什麼觸發 2 次?因爲我們做了 2 次 set,原因上面也詳細說過了。

那麼這個時候,其實我們就有一個特別巨大的問題,什麼問題呢?

就是說,我們要如何才能只 render 一次。

 

首先肯定一點,我就 render 兩次了,怎麼滴?

其實也沒事,但是性能上會有很大的負擔。

而且說句特別實在的話,你現在做一個 push,它是 render 兩次,如果我們這個數組多弄幾個:

我不知道大家有沒有想過一個問題,如果我去做一個 shift 呢?

shift 是什麼操作? shift 是從頭上刪掉一個。

什麼叫從頭上刪掉一個?簡單來說,就是原來的 1 號元素,要來到 0 的位置、第 2 號要來到 1 的位置,第 3 號要來到 2 的位置,等等,以及 length 還要變。

所以當你去做 shift 的時候:

你會發現,它足足給我 render 了 7 次。

而且,這還是在我這個數組東西比較少的前提下,如果我這東西多,比如幾百上千的,也是有可能的,那這時候就 happy 了:

你可以看到,413 次 render,這個感覺一定很爽。

啥樣的系統能經得起你這麼搞呀?肯定不行。

所以我們必須得想辦法把這個重複的渲染給它幹掉。

 

當然,辦法是有的,也很多,但是你要幹掉的,僅僅是這個 proxy 自身給你帶來的重複渲染嗎?並不是。

用戶操作數據,有沒有可能同時操作了很多個東西?

比方說,他給這個數組 push 了一個東西,他又給那個 title 改了一下,他又給一個 user 刪了一個東西等等,這個用戶同時操作很多個數據,這都是有可能的。

 

所以,現在的問題就是,怎麼樣能夠把這些渲染操作,給它合併爲只有一個。

其實挺簡單的,我們有很多正統的方法,但是比較麻煩,這裏我們直接搞個野路子,怎麼做呢?

首先,我這會弄個定時器,但不是立刻就去做渲染,而是要經過一個小小的延時,也不用太大,0 毫秒就可以了:

然後,我還要做一個事。

因爲我們這個定時器有可能被多次設置,如果就只是這麼寫的話,沒有任何效果。

所以我們要提前去 clearTimeout,把這個 timer 先給它清了:

什麼意思呢?

就是說,如果在極快的速度之下,多次的執行 render,其實我就變成了一個,先把上一次的取消,在開個新的,然後我又被取消,又來一個新的,它剩的是不是最後一次啊?

我們直接來看看效果,給這個數組 push 和 shift:

可以看到,就一次 render,多了沒有。

其實我們在這,是把這個同步操作,化爲了一個異步操作。

首先很明確的說,定時器,它是一個異步操作,並且,我們 js 裏面的定時器都有一個特點,就是說,它先要等當前的那個代碼執行完,然後等到都完事了,它纔有機會去檢查定時器,哪怕到時候一檢查,發現是個 0 毫秒,它趕緊執行,無所謂,它也達到我的目的變成異步的了。

所以說白了,它會把定時器裏面的代碼,優先級往後放,別人都執行完了在走你(異步執行如果不太懂,可以看下前面的 js 基礎)

 

所以到目前爲止,我們就利用了一種野路子的方式,來幫助我們把這個 render 操作,都給它歸成了一個,就執行最後一次,多了沒有。

那麼現在我們做了兩件事:

第一,我們把這個數組變成可響應的了。

第二,我們做了一個 render,並且完成了一個最重要的工作,就是把這些額外的、無效的、沒有意義的 render 給它取消掉了。

 

然後接下來我們要做的就是,我要按照數組的值,來 render 出東西來。

並且最開始我們也說了,不像以前一樣,直接拿 innerHTML 直接堆,那個太亂了。

所以現在我要做一件事,我提前把作爲模版的這個元素,給它存起來,怎麼存呢?

注意,如果到這爲止,什麼都不做,你會發現,頁面上那個元素消失了:

你可以看到,控制檯也沒報錯,那麼元素去哪了呢?

我們上一篇博客是不是剛好說過,當我們去把一個元素添加一個新的父級下的時候,它會自動的跟原來那個父級解除關係,並且自動的進入到新的父級裏邊,那原來的父級也就沒這個東西了。

所以當我把 box 拉到 frag 裏面來的時候,那頁面上自然就沒有了。

 

然後在 render 裏面,我們就不去做 console 了,我要拿着數組,來做一個循環,每一次我都需要拿着這個 frag,來 cloneNode:

爲什麼?這樣的話,就完全跟它自己那個保持一致,它頁面裏面怎麼寫,我這就是怎麼寫,就這麼簡單。

然後這時候我就得到了一個新的元素 el,那麼接下來最後一步,我們需要幹什麼?

只需要把 el 插到頁面裏面去就行了,並且我們也知道,在渲染的過程當中,我們一般是先把它原來的那些東西全都給它清空:

那麼現在這時候,你可以看到,頁面上還是沒有:

因爲我們現在這個 render 什麼時候執行?只有當數據變化的時候才執行。

那換句話說,一開始是空的就不奇怪了,所以我們一開始就要做一次初始渲染。

那麼現在你可以看到,頁面就有東西了。

當然我們還有一個小小的要求,就是它上面顯示的都是寫死的 sss,我希望它這裏面能夠放我數組裏面的值:

那麼我們就加上。

el.children[0] 就是我們裏面一個個 class = box 的 div,我要它裏面的 h2,它的 innerHTML 就變成我們數組裏的元素。

這時候你可以看到,就變了:

以及,我能不能給它稍微的加點什麼:

所以說在這種情況下,你就發現了,非常的簡單。

我們現在就做到了,讓這個數組變化的同時,UI 界面直接去響應它的這種變化。

 

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