JS對大量數(shù)據(jù)進(jìn)行多重過濾的方法
前言
主要的需求是前端通過 Ajax 從后端取得了大量的數(shù)據(jù),需要根據(jù)一些條件過濾,首先過濾的方法是這樣的:
class Filter {
filterA(s) {
let data = this.filterData || this.data;
this.filterData = data.filter(m => m.a === s);
}
filterB(s) {
let data = this.filterData || this.data;
this.filterData = data.filter(m => m.b === s);
}
}
現(xiàn)在迷糊了,覺得這樣處理數(shù)據(jù)不對,但是又不知道該怎么處理。
發(fā)現(xiàn)問題
問題就在過濾上,這樣固然可以實現(xiàn)多重過濾(先調(diào)用 filterA() 再調(diào)用 filterB() 就可以實現(xiàn)),但是這個過濾是不可逆的。
假如過濾過程是這樣:
f.filterA("a1");
f.filterB("b1");
f.filterA("a2");
本來是希望按 "a1" 和 "b1" 過濾了數(shù)據(jù)之后,再修改第一個條件為 "a2",但結(jié)果卻成了空集。
解決問題
發(fā)現(xiàn)了問題,就針對性的解決。這個問題既然是因為過濾過程不可逆造成的,那每次都直接從 this.data 開始過濾,而不是從 this.filterData 開始過濾,就能解決問題。如果要這樣做,就需要將選擇的過濾條件先記錄下來。
記錄過濾條件
用一個列表記錄過濾條件當(dāng)然是可行的,但是注意對同一個條件的兩次過濾是互斥的,只能保留最后一個,所以應(yīng)該用 HashMap 更為合適。
class Filter {
constructor() {
this.filters = {};
}
set(key, filter) {
this.filters[key] = filter;
}
getFilters() {
return Object.keys(this.filters).map(key => this.filters[key]);
}
}
這種情況下,像上面的過程表示為
f.set("A", m => m.a === "a1");
f.set("B", m => m.b === "b1");
f.set("A", m => m.a === "a1");
let filters = f.getFilters(); // length === 2;
上面第 3 句設(shè)置的 filter 覆蓋了第 1 句設(shè)置的那個。現(xiàn)在再用最后取得的 filters 依次來過濾原數(shù)據(jù) this.data,就能得到正確的結(jié)果。
有人會覺得 getFilters() 返回的列表不是按 set 的順序的——的確,這是 HashMap 的特點,無序。不過對于簡單條件的判斷,不管誰先誰后,結(jié)果是一樣的。但是對于一些復(fù)合條件判斷,就可能會有影響。
確實需要的話,可以通過 array 代替 map 來解決一下順序的問題,但這樣查找效率會降低(線性查找)。如果還想解決查找效率的問題,可以用 array + map 來處理。這里就不多說了。
過濾
實際上在使用的時候,每次都 getFilter() 再用一個循環(huán)來處理確實比較慢。既然 data 都封裝成 Filter 中,可以考慮直接給一個 filter() 方法來送貨過濾接口。
class Filter {
filter() {
let data = this.data;
for (let f of this.getFilters()) {
data = data.filter(f);
}
return data;
}
}
不過這樣我覺得效率不太好,尤其是對大量數(shù)據(jù)的時候。不妨利用一下 lodash 的延遲處理過程。
利用 lodash 的延遲處理
filter() {
let chain = _(this.data);
for (let f of this.getFilters()) {
chain = chain.filter(f);
}
return chain.value();
}
lodash 在數(shù)據(jù)大于 200 的時候會啟用延遲處理過程,也就是說,它會處理成一個循環(huán)中依次調(diào)用每一個 filter,而不是對每一個 filter 進(jìn)行一次循環(huán)。
延遲處理和非延遲處理通過下圖可以看出來區(qū)別。非延遲處理總共會進(jìn)行 n(這里 n = 3) 次大循環(huán),產(chǎn)生 n - 1 個中間結(jié)果。而延遲處理只會進(jìn)行一次大循環(huán),沒有中間結(jié)果產(chǎn)生。

不過說實在的,我不太喜歡為了一點小事多加載一個庫,所以干脆自己做個簡單的實現(xiàn)
自己實現(xiàn)延遲處理
filter() {
const filters = this.getFilters();
return data.filter(m => {
for (let f of filters) {
// 如果某個 filter 已經(jīng)把它過濾掉了,也不用再用后面的 filter 來判斷了
if (!f(m)) {
return false;
}
}
return true;
});
}
里面的 for 循環(huán)還可以用 Array.prototype.every 來簡化:
filter() {
const filters = this.getFilters();
return data.filter(m => {
return filters.every(f => f(m));
});
}
數(shù)據(jù)過濾其實并不是多復(fù)雜的事情,只要把思路理清楚,搞明白什么數(shù)據(jù)是需要保留的,什么數(shù)據(jù)是臨時(中間過程)的,什么數(shù)據(jù)是最終結(jié)果……利用 Array.prototype 中的相關(guān)方法,或者諸如 lodash 之類的工具,很容易就處理出來了。
總結(jié)
以上就是這篇文章的全部內(nèi)容了,希望本文的內(nèi)容對大家的學(xué)習(xí)或者工作能有所幫助,如果有疑問大家可以留言交流。
相關(guān)文章
Js生成隨機數(shù)/隨機字符串的方法小結(jié)【5種方法】
這篇文章主要介紹了Js生成隨機數(shù)/隨機字符串的方法,結(jié)合實例形式總結(jié)分析了5種Js生成隨機數(shù)/隨機字符串的操作技巧,需要的朋友可以參考下2020-05-05
ES6中的Promise.all()和Promise.race()函數(shù)的實現(xiàn)方法
這篇文章主要介紹了ES6的Promise.all()和Promise.race()函數(shù),結(jié)合實例代碼介紹了ES6 Promise.race和Promise.all方法使用,通過實例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-02-02
javascript把15位身份證轉(zhuǎn)成18的函數(shù)
非常不錯的,看了這個大家就明白身份證的運算規(guī)則了2008-10-10

