吾輩的博客原文: https://blog.rxliuli.com/p/fc...
雖然說人人平等,但有些人更加平等。
爲何有了 Lodash 這種通用函數工具庫,吾輩要寫這篇文章呢?吾輩在 SegmentFault 上常常看到關於 JavaScript 數組的相關疑問,甚至於,相同類型的問題,只是數據變化了一些,就直接提出了一個新的問題(實際上,對自身並沒有幫助)。簡單搜索了一下 Array,竟然有 2360+ 條的結果,足可見這類問題的頻率之高。如果有一篇適合 JavaScript 萌新閱讀的本身實現數組更多操做的文章,狀況或許會發生一些變化。javascript
下面吾輩便來實現如下幾種常見的操做前端
uniqueBy
: 去重sortBy
: 排序filterItems
: 過濾掉一些元素diffBy
: 差別groupBy
: 分組arrayToMap
: Array 轉換爲 Map前言:
你至少須要瞭解 ES6 的一些特性你才能愉快的閱讀
uniqueBy
: 去重相關問題java
/** * js 的數組去重方法 * @param arr 要進行去重的數組 * @param kFn 惟一標識元素的方法,默認使用 {@link returnItself} * @returns 進行去重操做以後獲得的新的數組 (原數組並未改變) */ function uniqueBy(arr, kFn = val => val) { const set = new Set() return arr.filter((v, ...args) => { const k = kFn(v, ...args) if (set.has(k)) { return false } set.add(k) return true }) }
使用編程
console.log(uniqueBy([1, 2, 3, '1', '2'])) // [ 1, 2, 3, '1', '2' ] console.log(uniqueBy([1, 2, 3, '1', '2'], i => i + '')) // [ 1, 2, 3 ]
sortBy
: 排序相關問題segmentfault
/** * 快速根據指定函數對數組進行排序 * 注: 使用遞歸實現,對於超大數組(其實前端的數組不可能特別大吧?#笑)可能形成堆棧溢出 * @param arr 須要排序的數組 * @param kFn 對數組中每一個元素都產生可比較的值的函數,默認返回自身進行比較 * @returns 排序後的新數組 */ function sortBy(arr, kFn = v => v) { // TODO 此處爲了讓 typedoc 能生成文檔而不得不加上類型 const newArr = arr.map((v, i) => [v, i]) function _sort(arr, fn) { // 邊界條件,若是傳入數組的值 if (arr.length <= 1) { return arr } // 根據中間值對數組分治爲兩個數組 const medianIndex = Math.floor(arr.length / 2) const medianValue = arr[medianIndex] const left = [] const right = [] for (let i = 0, len = arr.length; i < len; i++) { if (i === medianIndex) { continue } const v = arr[i] if (fn(v, medianValue) <= 0) { left.push(v) } else { right.push(v) } } return _sort(left, fn) .concat([medianValue]) .concat(_sort(right, fn)) } return _sort(newArr, ([t1, i1], [t2, i2]) => { const k1 = kFn(t1, i1, arr) const k2 = kFn(t2, i2, arr) if (k1 === k2) { return 0 } else if (k1 < k2) { return -1 } else { return 1 } }).map(([_v, i]) => arr[i]) }
使用數組
console.log(sortBy([1, 3, 5, 2, 4])) // [ 1, 2, 3, 4, 5 ] console.log(sortBy([1, 3, 5, '2', '4'])) // [ 1, '2', 3, '4', 5 ] console.log(sortBy([1, 3, 5, '2', '4'], i => -i)) // [ 5, '4', 3, '2', 1 ]
filterItems
: 過濾掉一些元素相關問題函數式編程
/** * 從數組中移除指定的元素 * 注: 時間複雜度爲 1~3On * @param arr 須要被過濾的數組 * @param deleteItems 要過濾的元素數組 * @param kFn 每一個元素的惟一鍵函數 */ function filterItems(arr, deleteItems, kFn = v => v) { const kSet = new Set(deleteItems.map(kFn)) return arr.filter((v, i, arr) => !kSet.has(kFn(v, i, arr))) }
使用函數
console.log(filterItems([1, 2, 3, 4, 5], [1, 2, 0])) // [ 3, 4, 5 ] console.log(filterItems([1, 2, 3, 4, 5], ['1', '2'], i => i + '')) // [ 3, 4, 5 ]
diffBy
: 差別相關問題工具
/** * 比較兩個數組的差別 * @param left 第一個數組 * @param right 第二個數組 * @param kFn 每一個元素的惟一標識產生函數 * @returns 比較的差別結果 */ function diffBy(left, right, kFn = v => v) { // 首先獲得兩個 kSet 集合用於過濾 const kThanSet = new Set(left.map(kFn)) const kThatSet = new Set(right.map(kFn)) const leftUnique = left.filter((v, ...args) => !kThatSet.has(kFn(v, ...args))) const rightUnique = right.filter( (v, ...args) => !kThanSet.has(kFn(v, ...args)), ) const kLeftSet = new Set(leftUnique.map(kFn)) const common = left.filter((v, ...args) => !kLeftSet.has(kFn(v, ...args))) return { left: leftUnique, right: rightUnique, common } }
使用code
console.log(diffBy([1, 2, 3], [2, 3, 4])) // { left: [ 1 ], right: [ 4 ], common: [ 2, 3 ] } console.log(diffBy([1, 2, 3], ['2', 3, 4])) // { left: [ 1, 2 ], right: [ '2', 4 ], common: [ 3 ] } console.log(diffBy([1, 2, 3], ['2', 3, 4], i => i + '')) // { left: [ 1 ], right: [ 4 ], common: [ 2, 3 ] }
groupBy
: 分組相關問題
/** * js 數組按照某個條件進行分組 * * @param arr 要進行分組的數組 * @param kFn 元素分組的惟一標識函數 * @param vFn 元素分組的值處理的函數。第一個參數是累計值,第二個參數是當前正在迭代的元素,若是你使用過 {@link Array#reduce} 函數的話應該對此很熟悉 * @param init 每一個分組的產生初始值的函數。相似於 reduce 的初始值,但它是一個函數,避免初始值在全部分組中進行累加。 * @returns 元素標識 => 數組映射 Map */ function groupBy( arr, kFn = v => v, /** * 默認的值處理函數 * @param res 最終 V 集合 * @param item 當前迭代的元素 * @returns 將當前元素合併後的最終 V 集合 */ vFn = (res, item) => { res.push(item) return res }, init = () => [], ) { // 將元素按照分組條件進行分組獲得一個 條件 -> 數組 的對象 return arr.reduce((res, item, index, arr) => { const k = kFn(item, index, arr) // 若是已經有這個鍵了就直接追加, 不然先將之初始化再追加元素 if (!res.has(k)) { res.set(k, init()) } res.set(k, vFn(res.get(k), item, index, arr)) return res }, new Map()) }
使用
console.log(groupBy([1, 2, 2, 2, 4, 4, 5, 5, 6], i => i)) // Map { 1 => [ 1 ], 2 => [ 2, 2, 2 ], 4 => [ 4, 4 ], 5 => [ 5, 5 ], 6 => [ 6 ] } console.log(groupBy([1, 2, 2, 2, 4, 4, 5, 5, 6], i => i % 2 === 0)) // Map { false => [ 1, 5, 5 ], true => [ 2, 2, 2, 4, 4, 6 ] } console.log( groupBy( [1, 2, 2, 2, 4, 4, 5, 5, 6], i => i % 2 === 0, (res, i) => res.add(i), () => new Set(), ), ) // Map { false => Set { 1, 5 }, true => Set { 2, 4, 6 } }
arrayToMap
: 轉換爲 Map相關問題
/** * 將數組映射爲 Map * @param arr 數組 * @param k 產生 Map 元素惟一標識的函數,或者對象元素中的一個屬性名 * @param v 產生 Map 值的函數,默認爲返回數組的元素,或者對象元素中的一個屬性名 * @returns 映射產生的 map 集合 */ export function arrayToMap(arr, k, v = val => val) { const kFn = k instanceof Function ? k : item => Reflect.get(item, k) const vFn = v instanceof Function ? v : item => Reflect.get(item, v) return arr.reduce( (res, item, index, arr) => res.set(kFn(item, index, arr), vFn(item, index, arr)), new Map(), ) }
使用
const county_list = [ { id: 1, code: '110101', name: '東城區', citycode: '110100', }, { id: 2, code: '110102', name: '西城區', citycode: '110100', }, { id: 3, code: '110103', name: '崇文區', citycode: '110100', }, ] console.log(arrayToMap(county_list, 'code', 'name')) // Map { '110101' => '東城區', '110102' => '西城區', '110103' => '崇文區' } console.log(arrayToMap(county_list, ({ code }) => code, ({ name }) => name)) // Map { '110101' => '東城區', '110102' => '西城區', '110103' => '崇文區' }
相關問題
以上種種操做皆是對一層數組進行操做,若是咱們想對嵌套數組進行操做呢?例如上面這兩個問題?其實問題是相似的,只是遞歸遍歷數組而已。
/** * js 的數組遞歸去重方法 * @param arr 要進行去重的數組 * @param kFn 惟一標識元素的方法,默認使用 {@link returnItself},只對非數組元素生效 * @returns 進行去重操做以後獲得的新的數組 (原數組並未改變) */ function deepUniqueBy(arr, kFn = val => val) { const set = new Set() return arr.reduce((res, v, i, arr) => { if (Array.isArray(v)) { res.push(deepUniqueBy(v)) return res } const k = kFn(v, i, arr) if (!set.has(k)) { set.add(k) res.push(v) } return res }, []) }
使用
const testArr = [ 1, 1, 3, 'hello', [3, 4, 4, 'hello', '5', [5, 5, ['a', 'r']]], { key: 'test', }, 4, [3, 0, 2, 3], ] console.log(deepUniqueBy(testArr)) // [ 1, 3, 'hello', [ 3, 4, 'hello', '5', [ 5, [Object] ] ], { key: 'test' }, 4, [ 3, 0, 2 ] ]
事實上,目前 SegmentFault 上存在着大量低質量且重複的問題及回答,關於這點確實比不上 StackOverflow。下面是兩個例子,能夠看一下可否發現什麼問題
事實上,無論是問題仍是答案,都沒有突出核心 -- Array 映射爲 Map/Array 分組,並且這種問題和答案還層出不窮。若是對 Array 的 API 都沒有看過一遍就來詢問的話,對於幫助者來講倒是太失禮了!
JavaScript 對函數式編程支持很好,因此習慣高階函數於咱們而言是一件好事,將問題的本質抽離出來,而不是每次都侷限於某個具體的問題上。