首頁  >  文章  >  web前端  >  JavaScript中對大量資料的多重過濾

JavaScript中對大量資料的多重過濾

高洛峰
高洛峰原創
2016-11-08 09:58:031206瀏覽

所有程式碼使用 ES2015 語法,需要 ES5 語法的可以用 Babel - Try it out 或 TypeScript Playground 翻譯。

問題提出

今天有朋友問我一個問題,前端透過Ajax 從後端取得了大量的數據,需要根據一些條件過濾,過濾的方法是這樣的:

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); 
    } 
}

現在迷糊了,覺得這樣處理數據不對,但是又不知道該怎麼處理。

發現問題

問題就在過濾上,這樣固然可以實現多重過濾(先調用 filterA() 再調用 filterB() 就可以實現),但是這個過濾是不可逆的。假如過濾過程是這樣:

f.filterA("a1"); 
f.filterB("b1"); 
f.filterA("a2");

本來是希望按 "a1" 和 "b1" 過濾了數據之後,再修改第一個條件為 "a2",但結果卻成了空集。

解決問題

發現了問題,就針對性的解決。這個問題既然是因為過濾過程不可逆造成的,那麼每次都直接從 this.data 開始過濾,而不是從this.filterData 開始過濾,就能解決問題。如果要這樣做,就需要將選擇的篩選條件先記錄下來。

記錄過濾條件

用一個列表記錄過濾條件當然是可行的,但是注意對同一個條件的兩次過濾是互斥的,只能保留最後一個,所以應該用 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 句設定的 filter 覆蓋了第 1 句設定的那個。現在再用最後取得的 filters 依序來過濾原資料 this.data,就能得到正確的結果。

有人會覺得 getFilters() 傳回的清單不是按 set 的順序的-的確,這是 HashMap 的特點,無序。不過對於簡單條件的判斷,不管誰先誰後,結果是一樣的。但是對於一些複合條件判斷,就可能會有影響。

確實需要的話,可以透過 array 取代 map 來解決一下順序的問題,但這樣查找效率會降低(線性查找)。如果還想解決找出效率的問題,可以用 array + map 來處理。這裡就不多說了。

過濾

實際上在使用的時候,每次都 getFilter() 再用一個循環來處理確實比較慢。既然 data 都封裝成 Filter 中,可以考慮直接給一個filter() 方法來送貨過濾介面。

class Filter { 
    filter() { 
        let data = this.data; 
        for (let f of this.getFilters()) { 
            data = data.filter(f); 
        } 
        return data; 
    } 
}

不過這樣我覺得效率不太好,尤其是對大量數據的時候。不妨利用一下 lodash 的延遲處理流程。

利用 lodash 的延遲處理

filter() { 
    let chain = _(this.data); 
    for (let f of this.getFilters()) { 
        chain = chain.filter(f); 
    } 
    return chain.value(); 
}

lodash 在資料大於 200 的時候會啟用延遲處理過程,也就是說,它會處理成一個循環中依次調用每一個 filter,而不是對每一個 filter 進行一次循環。

延遲處理和非延遲處理透過下圖可以看出來區別。非延遲處理總共會進行 n(這裡 n = 3) 次大循環,產生 n - 1 個中間結果。而延遲處理只會進行一次大循環,沒有中間結果產生。

JavaScript中對大量資料的多重過濾

不過說實在的,我不太喜歡為了一點小事多加載一個庫,所以乾脆自己做個簡單的實現

自己實現延遲處理

filter() { 
    const filters = this.getFilters(); 
    return data.filter(m => { 
        for (let f of filters) { 
            // 如果某个 filter 已经把它过滤掉了,也不用再用后面的 filter 来判断了 
            if (!f(m)) { 
                return false; 
            } 
        } 
        return true; 
    }); 
}

裡面的for 循環還可以用Array.prototype. every 來簡化:

filter() { 
    const filters = this.getFilters(); 
    return data.filter(m => { 
        return filters.every(f => f(m)); 
    }); 
}

資料過濾其實並不是多複雜的事情,只要把思路理清楚,搞明白什麼數據是需要保留的,什麼數據是臨時(中間過程)的,什麼數據是最終結果……利用Array.prototype 中的相關方法,或是諸如lodash 之類的工具,很容易就處理出來了。

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn