說明
下方是這次應用程式的資料,是主要的設定,他存在資料庫裏面。
說明一下它的用途:
mission
是一串設定資料,有不重複的ID,protos 屬性存了篩選器資料。
weekday
存了每天的設定,根據星期一到日,每天不同。weekday
儲存當天要執行的 mission 們的 id,有需要的時候,透過 mid 屬性去存取 mission
中的設定,使用他的篩選器 protos。
為了方便 mission
反查有哪個 weekday 使用了他,所以每個 mission
中還有個 weekday
陣列儲存這個訊息。
1 | const config = { |
情況
寫這個應用程式的過程中,我遇到一些狀況,然後有些想法。
巢狀資料結構
這裡指的是config.viewer
的部分。在這篇文章中我會先把這個部分說成「用 weekday 群組化」的資料。如果我要取出某一天中的 groups 陣列:
1
2
3var 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
12var 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
17weekday : {
"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
16weekday : [
{ 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