內功修煉之lodash——chunk、zip、groupBy、invokeMap方法

若是以爲沒有面試題,那麼lodash每個方法就能夠看成一個題目,能夠看着效果反過來實現,以不一樣的方法實現、多種方法實現,鞏固基礎。除了某些一瞬間就能夠實現的函數,下面抽取部分函數做爲試煉。時代在進步,下文全部的解法都採用es2015+前端

本文實現方法都是看效果倒推實現方法,並進行一些拓展和思考,和源碼無關。lodash這個庫在這裏更像一個題庫,給咱們刷題的es6

能收穫什麼:面試

  • 修煉代碼基本功,瞭解常見的套路
  • 瞭解到一些操做的英文命名和規範
  • 積累經驗,面對複雜邏輯問題能夠迅速解決
  • 也許能夠查到本身的js基礎知識的漏洞

概念:編程

  • SameValue標準: 目前已有等價的api——Object.is(a, b),表示a和b在SameValue標準下是否相等。Object.is和===不一樣的地方在於,能夠判斷NaN和NaN相等,可是0 和 -0是不相等
  • SameValueZero標準: 與SameValue差異僅僅在於,此標準下0和-0是相等的,Array.prototype.includes、Set.prototype.has內部就是使用SameValueZero

注意:api

  • 三星難度以上的會具體拓展和講解
  • 文中使用的基本都是數組原生api以及es6+函數式編程,代碼簡潔且過程清晰
  • 若是說性能固然是命令式好,實現起來稍微麻煩一些並且比較枯燥無味
  • 時代在進步,人生苦短,我選擇語法糖和api。面臨大數據的性能瓶頸,纔是考慮命令式編程的時候

chunk

  • 描述: _.chunk(array, [size=0]),將數組拆分紅多個 size 長度的塊,並組成一個新數組。 若是數組沒法被分割成所有等長的塊,那麼最後剩餘的元素將組成一個塊。
  • 難度係數: ★★
  • 建議最長用時:6min
// example
_.chunk(['a', 'b', 'c', 'd'], 2);
// => [['a', 'b'], ['c', 'd']]

_.chunk(['a', 'b', 'c', 'd'], 3);
// => [['a', 'b', 'c'], ['d']]
複製代碼
參考代碼
// 常規方法
    function chunk(arr, size = 0) {
      let current = 0;
      let temp = [];
      return arr.reduce((acc, cur, i) => {
        if (current++ < size) { temp.push(cur); } if (temp.length === size || i === arr.length - 1) { acc.push(temp); current = 0; temp = []; } return acc; }, []); } // 抽象派 function chunk(arr, size = 0) { let current = 0; let temp = []; return arr.reduce( (acc, cur, i) => temp.push(...(current++ < size ? [cur] : [])) === size || i === arr.length - 1 ? [...acc, temp, ...((current = 0), (temp = []), [])] : acc, [] ); } 複製代碼

zip系列

zip&unzip

  • _.zip([arrays])建立一個打包全部元素後的數組。第一個元素包含全部提供數組的第一個元素,第二個包含全部提供數組的第二個元素,以此類推。
  • 參數[arrays] (...Array),表示要處理的數組隊列
  • 返回值 (Array)是一個打包後的數組 _.unzip(array)相似 _.zip,接收一個打包後的數組而且還原爲打包前的狀態。
  • 難度係數: ★
  • 建議最長用時:2min * 2 = 4min

zip&unzip的例子數組

var zipped = _.zip(['fred', 'barney'], [30, 40], [true, false]);
// => [['fred', 30, true], ['barney', 40, false]]

_.unzip(zipped);
// => [['fred', 'barney'], [30, 40], [true, false]]
複製代碼
參考代碼
function zip(target, ...arrs) {
  return target.map((item, index) => [item, ...arrs.map(arr => arr[index])])
}


function unzip(arrs) {
  return arrs.reduce((acc, arr) => {
    arr.forEach((item, index) => {
      acc[index] = acc[index] || [];
      acc[index].push(item)
    })
    return acc
  }, [])
}
複製代碼

zipWith & unzipWith

  • _.zipWith相似_.zip, 它另外接受一個 iteratee 決定如何重組值。 iteratee 會調用每一組元素,最後返回一個打包後的數組
  • _.unzipWith(array, [iteratee=_.identity])另外接受一個 iteratee 來決定如何重組解包後的數組。iteratee 會傳入4個參數:(accumulator, value, index, group)。每組的第一個元素做爲初始化的值,返回一個解包後的數組
  • 難度係數: ★★
  • 建議最長用時:6min
// example
_.zipWith([1, 2], [10, 20], [100, 200], function(a, b, c) {
  return a + b + c;
});
// => [111, 222]

// unzipWith
var zipped = _.zip([1, 2], [10, 20], [100, 200]);
// => [[1, 10, 100], [2, 20, 200]]

_.unzipWith(zipped, _.add);
// => [3, 30, 300]
複製代碼
參考代碼
// zipWith稍微修改一下就能夠實現
function zipWith(target, ...arrs) {
  const iteratee = arrs.pop()
  return target.map((item, index) => [item, ...arrs.map(arr => arr[index])]).map(group => iteratee(...group))
}


function unzipWith(arrs, iteratee) {
// 使用惟一標記,避免`!result`的假值誤判
  const FIRST_FLAG = Symbol()
  return arrs.reduce((acc, arr) => {
    arr.forEach((item, index) => {
      acc[index] = acc[index] || []
      acc[index].push(item)
    })
    return acc
  }, []).map(group => group.reduce((result, cur, index, all) =>
    result === FIRST_FLAG ? cur : iteratee(result, cur, index, all)
  ), FIRST_FLAG)
}
複製代碼

zipObject & zipObjectDeep

  • _.zipObject([props=[]], [values=[]]),接受屬性key的數組和values的數組,返回每一對k-v造成的對象
  • 難度係數: ★
  • 建議最長用時:2min
//example
_.zipObject(['a', 'b'], [1, 2]);
// => { 'a': 1, 'b': 2 }
複製代碼
function zipObject(keys, values) {
  return keys.reduce((obj, key, index) => {
    obj[key] = values[index]
    return obj
  }, {})
}
複製代碼
  • _.zipObjectDeep([props=[]], [values=[]]),相似 _.zipObject,它還支持屬性路徑。
  • 難度係數: ★★★★
  • 建議最長用時:12min
// example
_.zipObjectDeep(['a.b[0].c', 'a.b[1].d'], [1, 2]);
// => { 'a': { 'b': [{ 'c': 1 }, { 'd': 2 }] } }
複製代碼
參考代碼
// 從 'a'或者'[1]'這種摳出真正的key: a、1
function getRealKey(currentKey) {
  return /\[(\d+)\]/.test(currentKey) ? RegExp.$1 : currentKey
}

function zipObjectDeep(keys, values) {
  return keys.reduce((obj, key, index) => {
    const path = key.split(/\.|\B(?=\[\d+\])|\b(?=\[\d+\])/)
    // 輔助變量temp,利用引用類型的特性遍歷和賦值整個對象
    let temp = obj
    // 預留一個空位,最後一下賦值
    while(path.length > 1) {
      const currentKey = path.shift()
      const realKey = getRealKey(currentKey)
      // 若是下一個是[1]這種,那麼就是數組,否則就是一個對象
      // 若是你想給下一層的屬性賦值,那麼就要提早準備好它上一層的結構
      temp[realKey] = temp[realKey] || (/\[(\d+)\]/.test(path[0]) ? [] : {})
      temp = temp[realKey]
    }
    // 最後一下就是賦值了
    const lastKey = getRealKey(path.shift())
    temp[lastKey] = values[index]
    return obj
  }, {})
}
複製代碼

關於正則key.split(/\.|\B(?=\[\d+\])|\b(?=\[\d+\])/)的分析:app

  • split是能夠傳入正則的哦,對匹配到的內容進行分割。除了普通的.,還要考慮相似[0]這種,這種須要匹配到邊界才能夠完美分割
  • 分紅3部分,.、單詞邊界+[數字]、非單詞邊界+[數字]
  • .匹配到的split一下就是 'a.b.c' => ['a', 'b', 'c']
  • 單詞邊界+[數字] 'a[1]' => ['a', '[1]']
  • 非單詞邊界+[數字] '[0][1]' => ['[0]', '[1]']
  • ?=是正向0寬斷言,也就是說/a(?=xx)/匹配前面是xx的字符a,且xx不歸入捕獲組中

groupBy

  • _.groupBy(collection, [iteratee=_.identity])key 是經 iteratee 處理的結果, value 是產生 key 的元素數組。 iteratee 會傳入1個參數:(value)。
  • 參數: collection (Array|Object)是須要遍歷的集合。[iteratee=_.identity] (Function|Object|string)是一個函數,這個函數會處理每個元素(和其餘By系列的方法都是同樣的,傳入函數和With同樣的效果,傳入字符串或者數組會先調用_.property)
  • 返回一個組成彙總的對象
  • 難度係數: ★★(若是不知道property方法實現,再加多兩星難度)
  • 建議最長用時:6min
// example
_.groupBy([6.1, 4.2, 6.3], Math.floor);
// => { '4': [4.2], '6': [6.1, 6.3] }

// 使用了 `_.property` 的回調結果
_.groupBy(['one', 'two', 'three'], 'length');
// => { '3': ['one', 'two'], '5': ['three'] }
複製代碼
參考代碼
function property(path) {
  return function (o) {
    const temp = Array.isArray(path) ? path : path.split(".");
    let res = o;
    while (temp.length && res) {
      res = res[temp.shift()];
    }
    return res;
  };
}

function groupBy(...arrs) {
  let iteratee = arrs.pop();
  iteratee = typeof iteratee === 'function' ? iteratee : property(iteratee);
  return arrs.reduce((acc, arr) => {
    arr.forEach(item => {
      const key = iteratee(item)
      ;(acc[key] || (acc[key] = [])).push(item) 
    })
    return acc
  }, {})
}
複製代碼

invokeMap

  • _.invokeMap(collection, path, [args])調用 path 的方法處理集合中的每個元素,返回處理的數組。 若是方法名是個函數,集合中的每一個元素都會被調用到。
  • 參數: collection (Array|Object)是須要遍歷的集合,path (Array|Function|string)是要調用的方法名 或者 這個函數會處理每個元素。[args] (...*)給方法傳入的參數
  • 返回數組結果
  • 難度係數: ★
  • 建議最長用時:3min
// example
_.invokeMap([[5, 1, 7], [3, 2, 1]], 'sort');
// => [[1, 5, 7], [1, 2, 3]]

_.invokeMap([123, 456], String.prototype.split, '');
// => [['1', '2', '3'], ['4', '5', '6']]
複製代碼
參考代碼
function invokeMap(arr, fn, ...args) {
  return arr.map(item => {
// 面對這種傳入函數手動調用的,都記得call/apply一下
    return (typeof fn === 'function' ? fn : arr[fn]).apply(item, args)
  })
}
複製代碼

lodash的數組和collection的方法就此告一段落了,其餘方法基本都是不須要1分鐘就能夠寫出來或者沒有什麼坑點的。後面是function系列,to be continueide

關注公衆號《不同的前端》,以不同的視角學習前端,快速成長,一塊兒把玩最新的技術、探索各類黑科技函數式編程

相關文章
相關標籤/搜索