說明
下方是這次應用程式的資料,是主要的設定,他存在資料庫裏面。
說明一下它的用途:
mission 是一串設定資料,有不重複的ID,protos 屬性存了篩選器資料。
weekday 存了每天的設定,根據星期一到日,每天不同。weekday 儲存當天要執行的 mission 們的 id,有需要的時候,透過 mid 屬性去存取 mission 中的設定,使用他的篩選器 protos。
為了方便 mission 反查有哪個 weekday 使用了他,所以每個 mission 中還有個 weekday 陣列儲存這個訊息。
| 1 | const config = { | 
情況
寫這個應用程式的過程中,我遇到一些狀況,然後有些想法。
- 巢狀資料結構 
 這裡指的是- config.viewer的部分。在這篇文章中我會先把這個部分說成「用 weekday 群組化」的資料。- 如果我要取出某一天中的 groups 陣列: - 1 
 2
 3- var day = "1"; 
 var groups = config.viewer.weekday["1"].groups
 // 好長啊...................................- 因為巢狀太多層了,導致在取值的時候都要點好幾層。所以往往想多一行變數指派,讓他看起來不要那麼長。 - 可以看底下第2項範例的第一行。 - 我在初期的時候,很直覺想到這樣的資料結構: - 「這是 viewer 的設定,viewer 底下有每天(weekday)的設定,每天的設定稍有不同,分成1234567,底下有他們各自的設定」 - 可是太多層的確會挺困擾的,所以如果沒有要管理大量設定,或是即使不歸在 viewer 底下也無所謂的資料,其實可以拿出來。 
 感覺上是這種巢狀盡量不要超過三層比較好理解。我自己在超過三層的時候腦袋想起來會很累。- 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24- //.................... 
 accomplishTag: "String",
 weekday : {
 "1" : {
 targetNum_total: 5,
 groups: [ // Loop 2
 { mid: "missionID", targetNum: 2 },
 { mid: "missionID2", targetNum: 3 },
 ]
 },
 "2" : {
 targetNum_total: 3,
 groups: [ // Loop 2
 { mid: "missionID3", targetNum: 1 },
 { mid: "missionID4", targetNum: 2 },
 ]
 }
 // 以下直到 "7".....
 },
 mission : {
 //................
 var groups = configs.weekday["1"].groups;
 // 這邊仍然是 4 層,.............................
- 運算對象是巢狀資料中的所有陣列 - 題目:在所有 weekday 中尋找某個 missionID 是否存在。在的話,從該 weekday 的 groups 中移除那個 mission。 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12- var weekdays = config.viewer.weekday; 
 var targetID = 'someID';
 for (const day in weekdays) {
 const daySetting = weekdays[day];
 const groups = daySetting.groups;
 // 尋找某個 mission 是否存在,在的話,移除
 var index = groups.findIndex(mission => mission.mid === targetID);
 if (index){
 groups.splice(index,1)
 }
 }- 這邊有兩個想法: - for in迴圈比較慢(其實通常沒有甚麼差別) 
- 雙重迴圈 - 這個連結比較了 for in 迭代物件,以及 forEach 迭代一般陣列的速度差。轉到 100000 圈的時候差大概 10 毫秒。除非是真的很有潔癖,否則這邊用不用 for in 其實不算是問題。但剛好我有點在意orz。 - 但就算不管 for in 的速度,這邊還是有個雙重迴圈,寫起來有點討厭。其實這個雙重迴圈的演算過程,在那個資料結構設定好的時候就應該可以預料到了。 - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17- weekday : { 
 "1" : { // Loop 1 for in 第一層迴圈
 targetNum_total: 5,
 groups: [ // Loop 2 forEach 第二層迴圈
 { mid: "missionID", targetNum: 2 },
 { mid: "missionID2", targetNum: 3 },
 ]
 },
 "2" : { // Loop 1 for in
 targetNum_total: 3,
 groups: [ // Loop 2 forEach
 { mid: "missionID3", targetNum: 1 },
 { mid: "missionID4", targetNum: 2 },
 ]
 }
 // 以下直到 "7".....
 },- 為了避免這種要轉兩層的很痛苦的感覺,我試著把結構改成這樣: - 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16- weekday : [ 
 { day:"1", mid: "missionID", targetNum: 2 },
 { day:"1", mid: "missionID2", targetNum: 3 },
 { day:"2", mid: "missionID3", targetNum: 1 },
 { day:"2", mid: "missionID4", targetNum: 2 },
 ]
 // 用這個資料結構做同一個題目
 var targetID = "someID";
 var index = weekday.findIndex(mission => mission.mid === targetID);
 while (index){
 groups.splice(index,1)
 index = groups.findIndex(mission => mission.mid === targetID);
 }
 // 結果還是轉了兩次迴圈啊哈哈哈哈哈- 結果還是轉了兩次迴圈啊哈哈哈哈哈~~。 
 再試著用兩種結構去算各種可能遇到的問題:- 1 
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27- // 1. 合計某一天的 targetNum_total 
 // 群組結構
 weekday["1"].groups
 .reduce((pre,now)=> pre.targetNum + now.targetNum)
 // 陣列結構
 weekday.filter(itm => itm.day === "1")
 .reduce((pre,now)=> pre.targetNum + now.targetNum)
 // 2. 找到某個 mission 在哪些 weekday 中
 // 群組結構
 var targetID = "missionID";
 var result = [];
 for (var day in weekday){
 var index = weekday[day].groups.findIndex(group => group.mid === targetID);
 if (index !== -1){
 result.push(day)
 }
 }
 // 陣列結構
 weekday.map(itm => {
 if (itm.mid === targetID){
 return itm.day
 }
 })- 如果我要針對某一天算的時候,群組結構比較省事;陣列結構反而要先 filter。 - 可是要跨群組的時候,群組結構必須要跑for in,而且每圈都要 findIndex。相對的陣列結構很省事。 
 
- 使用在 Vue 模板 
 欸,這個我就比較支持使用群組化的資料,至少就這次來說,以下是在表單中出現的狀況:- 1 
 2
 3
 4
 5
 6
 7
 8
 9- <!-- 使用巢狀結構 --> 
 <!-- 分群渲染 -->
 <div v-for="(daySetting, day) in configs.weekday" :key="day">
 <h3>{{ day }}</h3>
 <div v-for="(group,i) in daySetting.missions">
 <p>名稱:<input type="text" v-model="group.mid"> </p>
 <p>目標數量: <input type="number" v-model="group.targetNum"> </p>
 </div>
 </div>- 因為我需要讓使用者清楚看到某一天底下設定了什麼 mission,所以這種很符合直覺的巢狀結構反而適得其所。 - 相對的,陣列結構就比較麻煩了,需要外掛資料,寫個 computed 群組化將他群組化。 
結論
基本上巢狀結構能夠精簡就精簡比較好。但真的要拿來演算的時候,兩種結構各有優缺(廢話)。
整理一下:
- 陣列結構
    -  優點:
        1. 適合多屬性(props)交叉演算,因為所有資料都是同等的
        2. 彈性,可以自由根據屬性(props)群組化
    -  缺點:
        1. 針對特定領域(field)的演算需要先做一層filter
- 巢狀結構:
    -  優點:
        1. 特定情況下(群組內)查詢跟演算比較快 (包括用.運算子也是比較快的)
        2. 群組化,貼近直覺,方便理解
    -  缺點:
        1. 跨群組運算要多轉forin迴圈
        2. 難以用其他領域 (props) 組成資料
        3. 太多層會 ........... 到死
    -  建議:
        1. 適時精簡巢狀最大的問題是,在初期規劃階段,到底要怎麼決定(廢話)。
總之,要考量一下他會在那些場景使用,雖然曾經在開發紀錄3 思考篩選文章用的資料格式
稍微考慮過類似的問題,但沒想到其他地方也會發生這種算的有點不舒服的狀況。orz